1. 程式人生 > >Chromium硬體加速渲染的OpenGL上下文繪圖表面建立過程分析

Chromium硬體加速渲染的OpenGL上下文繪圖表面建立過程分析

       GPU命令需要在OpenGL上下文中執行。每一個OpenGL上下文都關聯有一個繪圖表面,GPU命令就是作用在繪圖表面上的。不同用途的OpenGL上下文關聯的繪圖表面不一樣,例如用於離屏渲染的OpenGL上下文關聯的繪圖表面可以用Pbuffer描述,而用於螢幕渲染的OpenGL上下文的繪圖表面要用本地視窗描述。本文接下來就分析Chromium硬體加速渲染涉及到的OpenGL上下文的繪圖表面建立過程。

《Android系統原始碼情景分析》一書正在進擊的程式設計師網(http://0xcc0xcd.com)中連載,點選進入!

       從前面Chromium的GPU程序啟動過程分析一文可以知道,在Chromium中,所有的GPU命令均在一個GPU執行緒中執行。這個GPU執行緒有可能是位於Browser程序中,也有可能是位於GPU程序中。需要執行GPU命令的有WebGL、Render程序和Browser程序,為了方便描述,我們稱之為WebGL端、Render端和Browser端,意為它們是Chromium的GPU執行緒的Client端。

       WebGL端和Render端執行的離屏渲染,而Browser端執行的螢幕渲染,因此它們需要的繪圖表面是不一樣的,如圖1所示:


圖1 Chromium的真實OpenGL上下文的繪圖表面

       從圖1可以看到,WebGL端、Render端和Browser端的OpenGL上下文均是通過GLContextEGL描述,不過它們關聯的繪圖表面卻是不一樣的。GLContextEGL描述的是一個真實的OpenGL上下文,這是相對於我們後面要描述的虛擬OpenGL上下文而言的。

       WebGL端和Render端的OpenGL上下文關聯的繪圖表面是GPU執行緒中的預設離屏繪圖表面,這個離屏繪圖表面用一個Pbuffer來描述。但是如果底層的GPU支援無繪圖表面的OpenGL上下文,那麼GPU執行緒的預設離屏繪圖表面使用一個Surfaceless描述。

       Browser端的OpenGL上下文關聯的繪圖表面是一個本地視窗,因為它負責將WebGL端、Render端和瀏覽器視窗其它的UI渲染在螢幕上。在Android平臺上,SurfaceView描述的就是一個本地視窗,因此,Chromium將它作為Browser端的OpenGL上下文的繪圖表面。

       在前面Chromium硬體加速渲染機制基礎知識簡要介紹和學習計劃一文提到,有些平臺的GPU不能很好地同時支援建立多個OpenGL上下文。對於這些平臺,Chromium為WebGL端、Render端和Browser端建立的是虛擬OpenGL上下文,如圖2所示:


圖2 Chromium的虛擬OpenGL上下文的繪圖表面

       虛擬OpenGL上下文使用GLContextVirtual來描述。由於它們是虛擬的,因此在啟用的時候,必須切換到真實的OpenGL上下文去。這意味著虛擬OpenGL上下文必須要對應有真實OpenGL上下文。

       Chromium在不能很好支援同時建立多個OpenGL上下文的平臺上,只建立一個真實OpenGL上下文,並且WebGL端、Render端和Browser端的虛擬OpenGL上下文均與該真實OpenGL上下文對應。上述真實OpenGL上下文為WebGL端和Render端所用時,關聯的繪圖表面是GPU執行緒的預設離屏繪圖表面,但是為Browser端所用時,關聯的繪圖表面是本地視窗。在Android平臺上,這個本地視窗即為一個SurfaceView。從這一點我們可以看到,OpenGL上下文的繪圖表面是可以動態修改的。

       從圖1和圖2還可以看到,不管WebGL端、Render端和Browser端的OpenGL上下文是真實的,還是虛擬的,它們都是位於一個共享組中。這使得Browser端的OpenGL上下文可以直接訪問在WebGL端和Render端的OpenGL上下文中建立的資源,從而可以高效地完成合成和渲染操作。接下來,我們就分別分析WebGL端、Render端和Browser端的OpenGL上下文的繪圖表面的建立過程。理解OpenGL上下文的繪圖表面的建立過程對理解OpenGL上下文的建立過程是至關重要的,因此我們專門用一篇文章來分析它們。

       我們首先分析WebGL端的OpenGL上下文的繪圖表面的建立過程。

       從前面Chromium的GPU程序啟動過程分析一文可以知道,WebGL端是通過一個WebGraphicsContext3DCommandBufferImpl物件向GPU執行緒請求執行GPU操作的。這個WebGraphicsContext3DCommandBufferImpl物件是通過呼叫WebGraphicsContext3DCommandBufferImpl類的靜態成員函式CreateOffscreenContext建立的,它的實現如下所示:

WebGraphicsContext3DCommandBufferImpl*
WebGraphicsContext3DCommandBufferImpl::CreateOffscreenContext(
    GpuChannelHost* host,
    const WebGraphicsContext3D::Attributes& attributes,
    bool lose_context_when_out_of_memory,
    const GURL& active_url,
    const SharedMemoryLimits& limits,
    WebGraphicsContext3DCommandBufferImpl* share_context) {
  ......

  return new WebGraphicsContext3DCommandBufferImpl(
      0,
      active_url,
      host,
      attributes,
      lose_context_when_out_of_memory,
      limits,
      share_context);
}
      這個函式定義在檔案external/chromium_org/content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.cc中。

      我們注意到,在呼叫WebGraphicsContext3DCommandBufferImpl類的建構函式建立一個WebGraphicsContext3DCommandBufferImpl物件的時候,第一個引數指定0,這表示WebGL端的OpenGL上下文使用的繪圖表面的ID為0。

       WebGraphicsContext3DCommandBufferImpl類的建構函式的實現如下所示:

WebGraphicsContext3DCommandBufferImpl::WebGraphicsContext3DCommandBufferImpl(
    int surface_id,
    const GURL& active_url,
    GpuChannelHost* host,
    const Attributes& attributes,
    bool lose_context_when_out_of_memory,
    const SharedMemoryLimits& limits,
    WebGraphicsContext3DCommandBufferImpl* share_context)
    : ......,
      surface_id_(surface_id),
      ...... {
  ......
}
       這個函式定義在檔案external/chromium_org/content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.cc中。

       這意味著WebGL端使用的WebGraphicsContext3DCommandBufferImpl物件的成員變數surface_id_的值等於0。Render程序在為WebGL端建立OpenGL上下文時,會將該成員變數的值傳遞到GPU執行緒,這時候GPU執行緒就知道要為正在建立的OpenGL上下文關聯的一個離屏繪圖表面。這個過程我們在接下來一篇文章中分析OpenGL上下文的建立過程時再分析,此時我們只需要記住WebGL端的OpenGL上下文關聯的繪圖表面的ID等於0。

       我們接下來分析Browser端的OpenGL上下文的繪圖表面的建立過程。這要從Browser程序的啟動過程說起。

       Chromium自帶了一個Shell APK,用來說明如何通過Content API來建立一個基於Chromium的瀏覽器。Chromium的Content API封裝了Chromium的多程序架構以及WebKit等,通過它們可以很容易地實現一個與Chrome類似的瀏覽器。Shell APK的原始碼位於目錄external/chromium_org/content/shell/android/shell_apk中。

       Shell APK的Main Activity是ContentShellActivity,它執行在Shell APK的主程序中,也就是Browser程序。當Shell APK啟動時,ContentShellActivity類的成員函式onCreate就會被呼叫,它的執行過程如下所示:

public class ContentShellActivity extends Activity {
    ......

    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ......

        setContentView(R.layout.content_shell_activity);
        mShellManager = (ShellManager) findViewById(R.id.shell_container);
        mWindowAndroid = new ActivityWindowAndroid(this);
        ......
        mShellManager.setWindow(mWindowAndroid);

        String startupUrl = getUrlFromIntent(getIntent());
        if (!TextUtils.isEmpty(startupUrl)) {
            mShellManager.setStartupUrl(Shell.sanitizeUrl(startupUrl));
        }

        if (CommandLine.getInstance().hasSwitch(ContentSwitches.DUMP_RENDER_TREE)) {
            try {
                BrowserStartupController.get(this).startBrowserProcessesSync(
                       BrowserStartupController.MAX_RENDERERS_LIMIT);
            } catch (ProcessInitException e) {
                ......
            }
        } else {
            try {
                BrowserStartupController.get(this).startBrowserProcessesAsync(
                        new BrowserStartupController.StartupCallback() {
                            @Override
                            public void onSuccess(boolean alreadyStarted) {
                                finishInitialization(savedInstanceState);
                            }

                            ......
                        });
            } catch (ProcessInitException e) {
                ......
            }
        }
    }

    ......
}
       這個函式定義在檔案external/chromium_org/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java中。

       ContentShellActivity類的成員函式onCreate主要做了以下幾件事情:

       1. 將R.layout.content_shell_activity佈局檔案設定為瀏覽器視窗UI。

       2. 在上述佈局檔案中找到一個id為shell_container的控制元件,該控制元件是一個自定義控制元件,型別為ShellManager,儲存在ContentShellActivity類的成員變數mShellManager中。

       3. 建立一個ActivityWindowAndroid物件,儲存在ContentShellActivity類的成員變數mWindowAndroid中,並且呼叫ShellManager類的成員函式setWindow將其設定到上述ShellManager控制元件內部去。ActivityWindowAndroid物件封裝了對Shell APK的主Activity的一些操作。

       4. 檢查啟動Shell APK的主Activity的Intent是否設定有啟動URL。如果有,那麼就通過呼叫ShellManager類的成員函式setStartupUrl將其設定到上述ShellManager控制元件內部去。

       5. 檢查Shell APK的命令列引數是否設定了ContentSwitches.DUMP_RENDER_TREE選項。如果設定了,那麼就呼叫Browser程序的BrowserStartupController單例的成員函式startBrowserProcessesSync同步啟動和初始化Content模組,相當於是在Native層對Browser程序執行初始化工作。如果沒有設定,那麼就呼叫Browser程序的BrowserStartupController單例的成員函式startBrowserProcessesAsync非同步啟動和初始化Content模組。對於非同步方式,當Content模組啟動和初始完成後,會呼叫ContentShellActivity類的成員函式finishInitialization繼續執行啟動Shell APK的工作。

       接下來,我們主要分析第2、3和5個步驟的相關操作,其中,假設第5步使用非同步方式啟動和初始化Content模組。

       第2步主要是建立了一個型別為ShellManager的控制元件,這將會導致ShellManager類的建構函式被呼叫,如下所示:

public class ShellManager extends FrameLayout {
    ......

    public ShellManager(final Context context, AttributeSet attrs) {
        super(context, attrs);
        nativeInit(this);
        ......
    }

    ......
}
      這個函式定義在檔案external/chromium_org/content/shell/android/java/src/org/chromium/content_shell/ShellManager.java中。

      ShellManager類的建構函式主要是呼叫了另外一個成員函式nativeInit執行一些初始化工作。

      ShellManager類的成員變數nativeInit是一個JNI函式,它由Native層的函式Init實現,如下所示:

struct GlobalState {
  GlobalState() {}
  base::android::ScopedJavaGlobalRef<jobject> j_shell_manager;
};

base::LazyInstance<GlobalState> g_global_state = LAZY_INSTANCE_INITIALIZER;

......

static void Init(JNIEnv* env, jclass clazz, jobject obj) {
  g_global_state.Get().j_shell_manager.Reset(
      base::android::ScopedJavaLocalRef<jobject>(env, obj));
}
       這個函式定義在檔案external/chromium_org/content/shell/android/shell_manager.cc中。

       從前面的呼叫過程可以知道,引數obj指向的是一個Java層的ShellManager物件,函式Init將它儲存在全域性變數g_global_state描述的一個GlobalState物件的成員變數j_shell_manager中。

       這一步執行完成之後,回到ContentShellActivity類的成員函式onCreate中,它的第3步主要是呼叫ShellManager類的成員函式setWindow設定一個ActivityWindowAndroid物件到Shell APK的主Activity視窗UI的ShellManager控制元件的內部去,它的實現如下所示:

public class ShellManager extends FrameLayout {
    ......

    public void setWindow(WindowAndroid window) {
        assert window != null;
        mWindow = window;
        mContentViewRenderView = new ContentViewRenderView(getContext()) {
            @Override
            protected void onReadyToRender() {
                if (sStartup) {
                    mActiveShell.loadUrl(mStartupUrl);
                    sStartup = false;
                }
            }
        };
        mContentViewRenderView.onNativeLibraryLoaded(window);
    }

    ......
}

       這個函式定義在檔案external/chromium_org/content/shell/android/java/src/org/chromium/content_shell/ShellManager.java

       ShellManager類的成員函式setWindow首先是將引數window描述的一個ActivityWindowAndroid物件儲存在成員變數mWindow中,接著建立了一個ContentViewRenderView物件,並且儲存在成員變數mContentViewRenderView中。當Shell APK啟動和初始化完畢,上面建立的ContentViewRenderView物件的成員函式onReadyRender就會被呼叫。在呼叫的過程中,就會呼叫ShellManager類的成員變數mActivityShell描述的一個Shell物件的成員函式loadUrl載入由另外一個成員變數mStartupUrl指定的一個URL。這個URL是設定在用來啟動Shell APK的Intent中的。ShellManager類的成員函式setWindow最後呼叫前面建立的ContentViewRenderView物件的成員函式onNativeLibraryLoaded對其進行初始化。

       接下來,我們首先分析ContentViewRenderView物件的建立過程,即ContentViewRenderView類的建構函式的實現,接著再分析ContentViewRenderView物件的初始化過程,即ContentViewRenderView類的成員函式onNativeLibraryLoaded的實現。

       ContentViewRenderView類的建構函式的實現如下所示:

public class ContentViewRenderView extends FrameLayout {
    ......

    private final SurfaceView mSurfaceView;
    ......

    public ContentViewRenderView(Context context) {
        super(context);

        mSurfaceView = createSurfaceView(getContext());
        ......

        addView(mSurfaceView,
                new FrameLayout.LayoutParams(
                        FrameLayout.LayoutParams.MATCH_PARENT,
                        FrameLayout.LayoutParams.MATCH_PARENT));
        ......
    }

    ......
}
       這個函式定義在檔案external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentViewRenderView.java中。

       從這裡可以看到,ContentViewRenderView類描述的是一個FrameLayout,它的建構函式主要是通過呼叫另外一個成員函式createSurfaceView建立了一個SurfaceView,儲存在成員變數mSurfaceView中,並且作為當前正在建立的ContentViewRenderView的子控制元件。

       這一步執行完成後,回到ShellManager類的成員函式setWindow中,它接下來呼叫ContentViewRenderView類的成員函式onNativeLibraryLoaded對前面建立的ContentViewRenderView進行初始化,如下所示:

public class ContentViewRenderView extends FrameLayout {
    // The native side of this object.
    private long mNativeContentViewRenderView;
    private SurfaceHolder.Callback mSurfaceCallback;
    ......

    public void onNativeLibraryLoaded(WindowAndroid rootWindow) {
        ......
        mNativeContentViewRenderView = nativeInit(rootWindow.getNativePointer());
        ......
        mSurfaceCallback = new SurfaceHolder.Callback() {
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                ......
                nativeSurfaceChanged(mNativeContentViewRenderView,
                        format, width, height, holder.getSurface());
                ......
            }

            ......
        };
        mSurfaceView.getHolder().addCallback(mSurfaceCallback);
 
        ......
    }
 
    ......
}
       這個函式定義在檔案external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentViewRenderView.java中。

       從前面的呼叫過程可以知道,引數rootWindow指向的是一個ActivityWindowAndroid物件,ContentViewRenderView類的成員函式onNativeLibraryLoaded首先呼叫該ActivityWindowAndroid物件的成員函式getNativePointer獲得一個Native層的WindowAndroid物件,然後再以該Native層的WindowAndroid物件為引數,呼叫ContentViewRenderView類的成員函式nativeInit對當前正在建立的ContentViewRenderView進行初始化。

       ContentViewRenderView類的成員函式onNativeLibraryLoaded還建立了一個SurfaceHolder.Callback物件,儲存在成員變數mSurfaceCallback中,並且使用該SurfaceHolder.Callback物件獲得成員變數mSurfaceView描述的一個SurfaceView底層所使用的繪圖表面的變化。也就是說,當ContentViewRenderView類的成員變數mSurfaceView描述的一個SurfaceView底層所使用的繪圖表面發生變化時,上述SurfaceHolder.Callback物件的成員函式surfaceChanged就會被呼叫,然後又會呼叫ContentViewRenderView類的成員函式nativeSurfaceChanged執行相關的操作。

      接下來,我們首先分析ActivityWindowAndroid類的成員函式getNativePointer的實現,接著再分析ContentViewRenderView類的成員函式nativeInit的實現。

      ActivityWindowAndroid類繼承於WindowAndroid類,它的成員函式getNativePointer也是從父類WindowAndroid繼承下來的,因此接下來我們分析WindowAndroid的成員函式getNativePointer的實現,如下所示:

public class WindowAndroid {
    ......

    private long mNativeWindowAndroid = 0;
    ......

    public long getNativePointer() {
        if (mNativeWindowAndroid == 0) {
            mNativeWindowAndroid = nativeInit(mVSyncMonitor.getVSyncPeriodInMicroseconds());
        }
        return mNativeWindowAndroid;
    }

    ......
}
       這個函式定義在檔案external/chromium_org/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java中。 

       WindowAndroid類的成員函式getNativePointer返回的是成員變數mNativeWindowAndroid描述的一個Native層的WindowAndroid物件。不過,當這個成員變數的值等於0的時候,就表示Native層的WindowAndroid物件還沒有建立,這時候就需要呼叫另外一個成員函式nativeInit進行建立。

       WindowAndroid類的成員函式nativeInit是一個JNI函式,由Native層的函式Init實現,如下所示:

jlong Init(JNIEnv* env, jobject obj, jlong vsync_period) {
  WindowAndroid* window = new WindowAndroid(env, obj, vsync_period);
  return reinterpret_cast<intptr_t>(window);
}
       這個函式定義在檔案external/chromium_org/ui/base/android/window_android.cc中。

       函式Init主要是建立了一個Native層的WindowAndroid物件,並且將它的地址返回給呼叫者。

       這一步執行完成後,回到ContentViewRenderView類的成員函式onNativeLibraryLoaded中,它接下來呼叫ContentViewRenderView類的成員函式nativeInit對正在建立的ContentViewRenderView進行初始化。

       ContentViewRenderView類的成員函式nativeInit是一個JNI函式,由Native層的函式Init實現,如下所示:

static jlong Init(JNIEnv* env,
                  jobject obj,
                  jlong native_root_window) {
  gfx::NativeWindow root_window =
      reinterpret_cast<gfx::NativeWindow>(native_root_window);
  ContentViewRenderView* content_view_render_view =
      new ContentViewRenderView(env, obj, root_window);
  return reinterpret_cast<intptr_t>(content_view_render_view);
}
       這個函式定義在檔案external/chromium_org/content/browser/android/content_view_render_view.cc中。

       從前面的呼叫過程可以知道,引數native_root_window指向的是一個Native層的WindowAndroid物件,函式Init將它封裝在一個新建立的Native層的ContentViewRenderView物件中,最後將新建立的Native層的ContentViewRenderView物件的地址返回給呼叫者。

       這一步執行完成後,就在Java層建立了一個SurfaceView,並且在Native層建立了一個WindowAndroid物件和一個ContentViewRenderView物件,回到ContentShellActivity類的成員函式onCreate中,它的第5步是呼叫Browser程序的BrowserStartupController單例的成員函式startBrowserProcessesAsync非同步啟動和初始化Content模組。當Content模組啟動和初始完成後,會呼叫ContentShellActivity類的成員函式finishInitialization繼續執行啟動Shell APK的工作,如下所示:

public class ContentShellActivity extends Activity {
    ......

    private void finishInitialization(Bundle savedInstanceState) {
        String shellUrl = ShellManager.DEFAULT_SHELL_URL;
        if (savedInstanceState != null
                && savedInstanceState.containsKey(ACTIVE_SHELL_URL_KEY)) {
            shellUrl = savedInstanceState.getString(ACTIVE_SHELL_URL_KEY);
        }
        mShellManager.launchShell(shellUrl);
    }

    ......
}
       這個函式定義在檔案external/chromium_org/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java中。

       ContentShellActivity類的成員函式finishInitialization主要是呼叫了成員變數mShellManager指向的一個ShellManager物件的成員函式launchShell啟動一個Shell,同時為該Shell指定了一個URL。該URL的預設值為ShellManager.DEFAULT_SHELL_URL,但是如果上次啟動Shell APK時,儲存有最後使用的Shell URL,那麼就會使用最後使用的Shell URL。

       ShellManager類的成員函式launchShell的實現如下所示:

public class ShellManager extends FrameLayout {
    ......

    public void launchShell(String url) {
        ......
        nativeLaunchShell(url);
        ......
    }

    ......
}
       這個函式定義在檔案external/chromium_org/content/shell/android/java/src/org/chromium/content_shell/ShellManager.java中。

       ShellManager類的成員函式launchShell主要是呼叫了另外一個成員函式nativeLaunchShell在Native層啟動一個Shell。

       ShellManager類的成員函式nativeLaunchShell是一個JNI函式,由Native層的函式LaunchShell實現,如下所示:

void LaunchShell(JNIEnv* env, jclass clazz, jstring jurl) {
  ShellBrowserContext* browserContext =
      ShellContentBrowserClient::Get()->browser_context();
  GURL url(base::android::ConvertJavaStringToUTF8(env, jurl));
  Shell::CreateNewWindow(browserContext,
                         url,
                         NULL,
                         MSG_ROUTING_NONE,
                         gfx::Size());
}

       這個函式定義在檔案external/chromium_org/content/shell/android/shell_manager.cc中。

       函式LaunchShell主要是呼叫另外一個函式Shell::CreateNewWindow在Native層啟動一個Shell。注意,在呼叫函式Shell::CreateNewWindow時,第四個引數的值指定為MSG_ROUTING_NONE,後面我們分析Render端的OpenGL上下文的繪圖表面的建立過程時,將會用到該引數。

       函式Shell::CreateNewWindow的實現如下所示:

Shell* Shell::CreateNewWindow(BrowserContext* browser_context,
                              const GURL& url,
                              SiteInstance* site_instance,
                              int routing_id,
                              const gfx::Size& initial_size) {
  WebContents::CreateParams create_params(browser_context, site_instance);
  create_params.routing_id = routing_id;
  create_params.initial_size = AdjustWindowSize(initial_size);
  WebContents* web_contents = WebContents::Create(create_params);
  Shell* shell = CreateShell(web_contents, create_params.initial_size);
  if (!url.is_empty())
    shell->LoadURL(url);
  return shell;
}
       這個函式定義在檔案external/chromium_org/content/shell/browser/shell.cc中。

       函式Shell::CreateNewWindow首先呼叫WebContents類的靜態成員函式Create建立了一個WebContentsImpl物件,接著再呼叫函式CreateShell建立了一個Shell,最後在引數url的值不等於空的情況下,呼叫前面建立的Shell的成員函式LoadUrl載入引數url描述的網址。

       後面我們分析Render端的OpenGL上下文的繪圖表面的建立過程時,我們再分析WebContents類的靜態成員函式Create建立WebContentsImpl物件的過程,接下來我們繼續分析函式CreateShell建立Shell的過程,如下所示:

Shell* Shell::CreateShell(WebContents* web_contents,
                          const gfx::Size& initial_size) {
  Shell* shell = new Shell(web_contents);
  shell->PlatformCreateWindow(initial_size.width(), initial_size.height());

  ......

  return shell;
}
       這個函式定義在檔案external/chromium_org/content/shell/browser/shell.cc中。

       函式CreateShell首先是建立了一個Shell物件,接著呼叫這個Shell物件的成員函式PlatformCreateWindow建立一個Shell視窗,如下所示:

void Shell::PlatformCreateWindow(int width, int height) {
  java_object_.Reset(AttachCurrentThread(), CreateShellView(this));
}
      這個函式定義在檔案external/chromium_org/content/shell/browser/shell_android.cc。

      Shell類的成員函式PlatformCreateWindow主要是呼叫另外一個函式CreateShellView建立一個Java層的Shell物件,並且儲存在成員變數java_object_中。

      函式CreateShellView的實現如下所示:

jobject CreateShellView(Shell* shell) {
  JNIEnv* env = base::android::AttachCurrentThread();
  jobject j_shell_manager = g_global_state.Get().j_shell_manager.obj();
  return Java_ShellManager_createShell(
      env,
      j_shell_manager,
      reinterpret_cast<intptr_t>(shell)).Release();
}
       這個函式定義在檔案external/chromium_org/content/shell/android/shell_manager.cc中。

       從前面的分析可以知道,全域性變數g_global_state指向的一個GlobalState物件的成員變數j_shell_manager描述的是一個Java層的ShellManager物件,這裡呼叫函式Java_ShellManager_createShell呼叫它的成員函式createShell,用來建立一個Java層的Shell物件。

       Java層的ShellManager類的成員函式createShell的實現如下所示:

public class ShellManager extends FrameLayout {
    ......

    private Object createShell(long nativeShellPtr) {
        ......
        LayoutInflater inflater =
                (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        Shell shellView = (Shell) inflater.inflate(R.layout.shell_view, null);
        shellView.initialize(nativeShellPtr, mWindow, mContentViewClient);

        ......

        showShell(shellView);
        return shellView;
    }

    ......
}
       這個函式定義在檔案external/chromium_org/content/shell/android/java/src/org/chromium/content_shell/ShellManager.java中。

       ShellManager類的成員函式createShell首先是根據R.layout.shell_view佈局檔案建立了一個Shell控制元件,接著Shell類的成員函式initialize對該控制元件進行初始化,最後呼叫另外一個成員函式showShell顯示該控制元件,如下所示:

public class ShellManager extends FrameLayout {
    ......

    private void showShell(Shell shellView) {
        shellView.setContentViewRenderView(mContentViewRenderView);
        addView(shellView, new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
        mActiveShell = shellView;
        ContentViewCore contentViewCore = mActiveShell.getContentViewCore();
        if (contentViewCore != null) {
            mContentViewRenderView.setCurrentContentViewCore(contentViewCore);
            ......
        }
    }

    ......
}
       這個函式定義在檔案external/chromium_org/content/shell/android/java/src/org/chromium/content_shell/ShellManager.java中。

       從前面的分析可以知道,ShellManager類的成員變數mContentViewRenderView描述的是一個ContentViewRenderView控制元件,該ContentViewRenderView控制元件包含有一個SurfaceView,ShellManager類的成員函式showShell又將該ContentViewRenderView控制元件作為引數shellView描述的一個Shell控制元件的子控制元件,最後引數shellView描述的Shell控制元件又作為當前正在處理的ShellManager控制元件的一個子控制元件。從這裡我們就看到Browser視窗的UI層次結構大致為:

ShellManager
--Shell
   --ContentViewRenderView
      --SurfaceView

       ShellManager類的成員函式showShell接下來將引數shellView描述的Shell控制元件儲存在成員變數mActiveShell中,並且呼叫該Shell控制元件的成員函式getContentViewCore獲得一個ContentViewCore物件。有了這個ContentViewCore物件,就將它設定到ShellManager類的成員變數mContentViewRenderView描述的ContentViewRenderView控制元件的內部去。這是通過呼叫ContentViewRenderView類的成員函式setCurrentContentViewCore實現的。

       ContentViewRenderView類的成員函式setCurrentContentViewCore的實現如下所示:

public class ContentViewRenderView extends FrameLayout {
    ......

    public void setCurrentContentViewCore(ContentViewCore contentViewCore) {
        ......
        mContentViewCore = contentViewCore;

        if (mContentViewCore != null) {
            ......
            nativeSetCurrentContentViewCore(mNativeContentViewRenderView,
                                            mContentViewCore.getNativeContentViewCore());
        } 
        ......
    }

    ......
}
       這個函式定義在檔案external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentViewRenderView.java中。

       ContentViewRenderView類的成員函式setCurrentContentViewCore首先將引數contentViewCore描述的一個ContentViewCore物件儲存在成員變數mContentViewCore中,接著呼叫該ContentViewCore物件的成員函式getNativeContentViewCore獲得它在Native層對應的一個ContentViewCoreImpl物件。

       從前面的分析可以知道,ContentViewRenderView類的成員變數mNativeContentViewRenderView描述的是Native層的一個ContentViewRenderView物件,ContentViewRenderView類的成員函式setCurrentContentViewCore最後呼叫另外一個成員函式nativeSetCurrentContentViewCore將該Native層的ContentViewRenderView物件,以及前面獲得的Native層的ContentViewCoreImpl物件設定到Native層的Chromium中去。

       ContentViewRenderView類的成員函式nativeSetCurrentContentViewCore是一個JNI函式,由Native層的函式Java_com_android_org_chromium_content_browser_ContentViewRenderView_nativeSetCurrentContentViewCore實現,如下所示:

__attribute__((visibility("default")))
void
    Java_com_android_org_chromium_content_browser_ContentViewRenderView_nativeSetCurrentContentViewCore(JNIEnv*
    env,
    jobject jcaller,
    jlong nativeContentViewRenderView,
    jlong nativeContentViewCore) {
  ContentViewRenderView* native =
      reinterpret_cast<ContentViewRenderView*>(nativeContentViewRenderView);
  CHECK_NATIVE_PTR(env, jcaller, native, "SetCurrentContentViewCore");
  return native->SetCurrentContentViewCore(env, jcaller, nativeContentViewCore);
}
       這個函式定義在檔案out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ContentViewRenderView_jni.h中。

       函式Java_com_android_org_chromium_content_browser_ContentViewRenderView_nativeSetCurrentContentViewCore首先是將引數nativeContentViewRenderView轉換為一個Native層的ContentViewRenderView物件,接著將引數nativeContentViewCore描述的一個Native層的ContentViewCoreImpl物件設定到其內部去,這是通過呼叫Native層的ContentViewRenderView類的成員函式SetCurrentContentViewCore實現的。

       Native層的ContentViewRenderView類的成員函式SetCurrentContentViewCore的實現如下所示:

void ContentViewRenderView::SetCurrentContentViewCore(
    JNIEnv* env, jobject obj, jlong native_content_view_core) {
  InitCompositor();
  ContentViewCoreImpl* content_view_core =
      reinterpret_cast<ContentViewCoreImpl*>(native_content_view_core);
  compositor_->SetRootLayer(content_view_core
                                ? layer_tree_build_helper_->GetLayerTree(
                                      content_view_core->GetLayer())
                                : scoped_refptr<cc::Layer>());
}
       這個函式定義在檔案external/chromium_org/content/browser/android/content_view_render_view.cc中。

       ContentViewRenderView類的成員函式SetCurrentContentViewCore首先呼叫另外一個成員函式InitCompositor建立一個在Browser程序使用的UI合成器,這個UI合成器儲存在成員變數compositor_中,它負責合成Render端負責渲染的網頁UI等。

       ContentViewRenderView類的成員函式SetCurrentContentViewCore接著將引數native_content_view_core轉化為一個Native層的ContentViewCoreImpl物件,並且將該ContentViewCoreImpl物件內部包含的一個Layer作為Browser程序的UI合成器的Root Layer,以後Browser程序的UI合成器通過遍歷該Root Layer描述的Layer Tree,就可以將Render端負責渲染的網頁UI合成到瀏覽器視窗來。

       接下來,我們繼續分析ContentViewRenderView類的成員函式InitCompositor的實現,以及可以瞭解Browser程序的UI合成器的建立過程,如下所示:

void ContentViewRenderView::InitCompositor() {
  if (!compositor_)
    compositor_.reset(Compositor::Create(this, root_window_));
}
       這個函式定義在檔案external/chromium_org/content/browser/android/content_view_render_view.cc中。

       從這裡可以看到,Browser程序的UI合成器是通過呼叫Compositor類的靜態成員函式Create建立的,它的實現如下所示:

Compositor* Compositor::Create(CompositorClient* client,
                               gfx::NativeWindow root_window) {
  return client ? new CompositorImpl(client, root_window) : NULL;
}
      這個函式定義在檔案external/chromium_org/content/browser/renderer_host/compositor_impl_android.cc中。

      從這裡可以看到,Browser程序的UI合成器通過CompositorImpl來描述,即它是一個CompositorImpl物件。

      這一步執行完成後,Browser程序在Native層中建立了一個Shell視窗和一個UI合成器,為以後合成Render端負責的網頁UI做好了準備。從前面的分析可以知道,Browser程序的Shell視窗包含有一個SurfaceView。在前面分析的ContentViewRenderView類的成員函式onNativeLibraryLoaded中又提到,當上述SurfaceView底層使用的繪圖表面發生變化(包括第一次建立時),ContentViewRenderView類的成員函式nativeSurfaceChanged會被呼叫。

       ContentViewRenderView類的成員函式nativeSurfaceChanged是一個JNI函式,由Native層的函式Java_com_android_org_chromium_content_browser_ContentViewRenderView_nativeSurfaceChanged實現,如下所示:

__attribute__((visibility("default")))
void
    Java_com_android_org_chromium_content_browser_ContentViewRenderView_nativeSurfaceChanged(JNIEnv*
    env,
    jobject jcaller,
    jlong nativeContentViewRenderView,
    jint format,
    jint width,
    jint height,
    jobject surface) {
  ContentViewRenderView* native =
      reinterpret_cast<ContentViewRenderView*>(nativeContentViewRenderView);
  CHECK_NATIVE_PTR(env, jcaller, native, "SurfaceChanged");
  return native->SurfaceChanged(env, jcaller, format, width, height, surface);
}
       這個函式定義在檔案out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ContentViewRenderView_jni.h中。

       函式Java_com_android_org_chromium_content_browser_ContentViewRenderView_nativeSurfaceChanged首先將引數nativeContentViewRenderView轉換為一個Native層的ContentViewRenderView物件,接著呼叫該ContentViewRenderView物件的成員函式SurfaceChanged通知Browser程序用來顯示網頁UI的SurfaceView發生了變化。

       ContentViewRenderView類的成員函式SurfaceChanged的實現如下所示:

void ContentViewRenderView::SurfaceChanged(JNIEnv* env, jobject obj,
    jint format, jint width, jint height, jobject surface) {
  if (current_surface_format_ != format) {
    current_surface_format_ = format;
    compositor_->SetSurface(surface);
  }
  ......
}
       這個函式定義在檔案external/chromium_org/content/browser/android/content_view_render_view.cc中。

       ContentViewRenderView類的成員變數current_surface_format_儲存的是Browser程序的SurfaceView底層使用的繪圖表面的顏色格式,它的值被初始化為0。因此,當Browser程序的SurfaceView第一次建立,以及它底層使用的繪圖表面的顏色格式發生變化時,ContentViewRenderView類的成員函式SurfaceChanged就會呼叫成員變數compositor_指向的一個CompositorImpl物件的成員函式SetSurface來重新設定Browser端的OpenGL上下文的繪圖表面。

       CompositorImpl類的成員函式SetSurface的實現如下所示:

void CompositorImpl::SetSurface(jobject surface) {
  JNIEnv* env = base::android::AttachCurrentThread();
  ......

  ANativeWindow* window = NULL;
  if (surface) {
    ......
    window = ANativeWindow_fromSurface(env, surface);
  }
  if (window) {
    SetWindowSurface(window);
    ......
  }
}
       這個函式定義在檔案external/chromium_org/content/browser/renderer_host/compositor_impl_android.cc中。

       CompositorImpl類的成員函式SetSurface首先呼叫函式ANativeWindow_fromSurface從引數surface描述的一個Java層的Surface物件獲得一個關聯的Native層的ANativeWindow物件,這個ANativeWindow物件描述的就是一個OS本地視窗。

       從前面的呼叫過程可以知道,引數surface描述的Java層的Surface物件描述的即為Browser程序的Shell視窗的SurfaceView底層所使用的繪圖表面,因此,前面獲得的ANativeWindow物件描述的OS本地視窗即為Browser程序的Shell視窗的SurfaceView。

       最後,CompositorImpl類的成員函式SetSurface呼叫另外一個成員函式SetWindowSurface將前面獲得的ANativeWindow物件設定為Browser端的OpenGL上下文的繪圖表面,它的實現如下所示:

void CompositorImpl::SetWindowSurface(ANativeWindow* window) {
  GpuSurfaceTracker* tracker = GpuSurfaceTracker::Get();
  ......

  if (window) {
    window_ = window;
    ......
    surface_id_ = tracker->AddSurfaceForNativeWidget(window);
    tracker->SetSurfaceHandle(
        surface_id_,
        gfx::GLSurfaceHandle(gfx::kNullPluginWindow, gfx::NATIVE_DIRECT));
    ......
  }
}
       這個函式定義在檔案external/chromium_org/content/browser/renderer_host/compositor_impl_android.cc中。

       CompositorImpl類的成員函式主要完成兩件事情:

       1. 呼叫Browser程序的GpuSurfaceTracker單例的成員函式AddSurfaceForNativeWidget為Browser端的OpenGL上下文建立一個繪圖表面,這個繪圖表面即為引數window描述的OS本地視窗。GpuSurfaceTracker類的成員函式AddSurfaceForNativeWidget的返回值為一個Surface ID,儲存在CompositorImpl類的成員變數surface_id_中,以後Browser端通過該Surface ID就可以在Browser程序的GpuSurfaceTracker單例中找到Browser端的OpenGL上下文的繪圖表面。

       2. 呼叫Browser程序的GpuSurfaceTracker單例的成員函式SetSurfaceHandle設定Browser端的OpenGL上下文的繪圖表面控制代碼的型別為gfx::NATIVE_DIRECT,表示Browser端的OpenGL上下文的繪圖表面可以直接渲染在螢幕上。

       接下來,我們就繼續分析GpuSurfaceTracker類的成員函式AddSurfaceForNativeWidget和SetSurfaceHandle的實現。

       GpuSurfaceTracker類的成員函式AddSurfaceForNativeWidget的實現如下所示:

int GpuSurfaceTracker::AddSurfaceForNativeWidget(
    gfx::AcceleratedWidget widget) {
  base::AutoLock lock(lock_);
  int surface_id = next_surface_id_++;
  surface_map_[surface_id] =
      SurfaceInfo(0, 0, widget, gfx::GLSurfaceHandle(), NULL);
  return surface_id;
}
       這個函式定義在檔案external/chromium_org/content/browser/gpu/gpu_surface_tracker.cc中。

       GpuSurfaceTracker類的成員函式AddSurfaceForNativeWidget首先將成員變數next_surface_id_的當前值作為新增加的繪圖表面的ID,並且將它的值增加1,作為下一次增加的繪圖表面的ID。

       GpuSurfaceTracker類的成員函式AddSurfaceForNativeWidget接著又建立了一個SurfaceInfo物件,該SurfaceInfo物件包含了引數widget描述的一個OS本地視窗,以及一個空的繪圖表面控制代碼,並且以前面獲得的ID值作為鍵值儲存在成員變數surface_map_描述的一個std::map中。

       GpuSurfaceTracker類的成員函式AddSurfaceForNativeWidget最後將分配給新增加的繪圖表面的ID返回給呼叫者。

       GpuSurfaceTracker類的成員函式SetSurfaceHandle的實現如下所示:

void GpuSurfaceTracker::SetSurfaceHandle(int surface_id,
                                         const gfx::GLSurfaceHandle& handle) {
  base::AutoLock lock(lock_);
  DCHECK(surface_map_.find(surface_id) != surface_map_.end());
  SurfaceInfo& info = surface_map_[surface_id];
  info.handle = handle;
}
       這個函式定義在檔案external/chromium_org/content/browser/gpu/gpu_surface_tracker.cc中。

       GpuSurfaceTracker類的成員函式SetSurfaceHandle首先根據引數surface_id描述的繪圖表面的ID在成員變數surface_map_描述的一個std::map中得到一個對應的SurfaceInfo物件,並且將該SurfaceInfo物件包含的繪圖表面控制代碼修改為引數handle描述的繪圖表面控制代碼。從前面的呼叫過程可以知道,引數handle描述的繪圖表面控制代碼的型別設定為gfx::NATIVE_DIRECT,在接下來一篇文章分析Browser端的OpenGL上下文的建立過程時將會使用到該型別值。

       Browser端和WebGL端、Render端一樣,所有的GPU操作都是要通過GPU執行緒執行的,因此它也像WebGL端、Render端一樣,需要一個WebGraphicsContext3DCommandBufferImpl物件與GPU執行緒進行通訊。

       Browser端的UI合成器將Render端負責渲染的網頁UI合成在一個Output Surface上。這個Output Surface對應的就是Browser程序的Shell視窗的SurfaceView,它是通過呼叫Browser端的UI合成器的成員函式CreateOutputSurface建立的,即呼叫CompositorImpl類的成員函式CreateOutputSurface建立的。

       CompositorImpl類的成員函式CreateOutputSurface在執行的過程中,就會建立一個WebGraphicsContext3DCommandBufferImpl物件,以便以後可以用來與GPU執行緒進行通訊,即請求GPU執行緒執行指定的GPU操作。

       CompositorImpl類的成員函式CreateOutputSurface的實現如下所示:

scoped_ptr<cc::OutputSurface> CompositorImpl::CreateOutputSurface(
    bool fallback) {
  ......

  scoped_refptr<ContextProviderCommandBuffer> context_provider;
  BrowserGpuChannelHostFactory* factory =
      BrowserGpuChannelHostFactory::instance();
  scoped_refptr<GpuChannelHost> gpu_channel_host = factory->GetGpuChannel();
  if (gpu_channel_host && !gpu_channel_host->IsLost()) {
    context_provider = ContextProviderCommandBuffer::Create(
        CreateGpuProcessViewContext(gpu_channel_host, attrs, surface_id_),
        "BrowserCompositor");
  }

  ......

  return scoped_ptr<cc::OutputSurface>(
      new OutputSurfaceWithoutParent(context_provider));
}
       這個函式定義在檔案external/chromium_org/content/browser/renderer_host/compositor_impl_android.cc中。

       CompositorImpl類的成員函式CreateOutputSurface首先是通過Browser程序的BrowserGpuChannelHostFactory單例的成員函式GetGpuChannel獲得一個GPU通道。這個GPU通道是用來在Browser程序和GPU程序之間進行通訊的,它的建立過程可以參考前面Chromium的GPU程序啟動過程分析一文。

       有了上述GPU通道之後,CompositorImpl類的成員函式CreateOutputSurface接著呼叫一個全域性函式CreateGpuProcessViewContext建立一個WebGraphicsContext3DCommandBufferImpl物件,如下所示:

static scoped_ptr<WebGraphicsContext3DCommandBufferImpl>
CreateGpuProcessViewContext(
    const scoped_refptr<GpuChannelHost>& gpu_channel_host,
    const blink::WebGraphicsContext3D::Attributes attributes,
    int surface_id) {
  ......

  return make_scoped_ptr(
      new WebGraphicsContext3DCommandBufferImpl(surface_id,
                                                url,
                                                gpu_channel_host.get(),
                                                attributes,
                                                lose_context_when_out_of_memory,
                                                limits,
                                                NULL));
}
       這個函式定義在檔案external/chromium_org/content/browser/renderer_host/compositor_impl_android.cc中。

       這裡我們重點關注的是呼叫WebGraphicsContext3DCommandBufferImpl類的建構函式建立一個WebGraphicsContext3DCommandBufferImpl物件時指定的第一個引數surface_id,它來自於CompositorImpl類的成員變數surface_id_,描述的是Browser端的OpenGL上下文的繪圖表面的ID,後面請求GPU執行緒為Browser端建立OpenGL上下文時,將會使用到該ID。

       函式CreateGpuProcessViewContext最後將創建出來的WebGraphicsContext3DCommandBufferImpl物件返回給CompositorImpl類的成員函式CreateOutputSurface,後者接下來呼叫ContextProviderCommandBuffer類的靜態成員函式Create將獲得的WebGraphicsContext3DCommandBufferImpl物件封裝在一個ContextProviderCommandBuffer物件,最後該ContextProviderCommandBuffer物件又會被封裝在一個OutputSurfaceWithoutParent物件中,作為Browser程序的UI合成器的Output Surface。

       這樣,我們就分析完成了Browser端的OpenGL上下文的繪圖表面的建立過程,這個繪圖表面是通過一個SurfaceView描述的,並且描述該繪圖表面的控制代碼(Surface Handle)的型別被設定為gfx::NATIVE_DIRECT。在接下來的一篇文章分析Browser端的OpenGL上下文的建立過程,我們就會看到這些資訊是如何使用的。      

       我們最後分析Render端的OpenGL上下文的繪圖表面的建立過程。

       同樣從前面Chromium的GPU程序啟動過程分析一文可以知道,Render端也是通過一個WebGraphicsContext3DCommandBufferImpl物件向GPU執行緒請求執行GPU操作的。這個WebGraphicsContext3DCommandBufferImpl物件是通過呼叫RenderWidget類的成員函式CreateGraphicsContext3D建立的,它的實現如下所示:

scoped_ptr<WebGraphicsContext3DCommandBufferImpl>  
RenderWidget::CreateGraphicsContext3D() {  
  ......  
  
  scoped_ptr<WebGraphicsContext3DCommandBufferImpl> context(  
      new WebGraphicsContext3DCommandBufferImpl(surface_id(),  
                                                GetURLForGraphicsContext3D(),  
                                                gpu_channel_host.get(),  
                                                attributes,  
                                                lose_context_when_out_of_memory,  
                                                limits,  
                                                NULL));  
  return context.Pass();  
}  
      這個函式定義在檔案external/chromium_org/content/renderer/render_widget.cc中。

      從這裡可以看到,RenderWidget類的成員函式CreateGraphicsContext3D在呼叫WebGraphicsContext3DCommandBufferImpl類的建構函式建立一個WebGraphicsContext3DCommandBufferImpl物件的時候,指定的繪圖表面的ID是通過呼叫RenderWidget類的成員函式surface_id獲得的,它的實現如下所示:

class CONTENT_EXPORT RenderWidget
    : public IPC::Listener,
      public IPC::Sender,
      NON_EXPORTED_BASE(virtual public blink::WebWidgetClient),
      public base::RefCounted<RenderWidget> {
 public:
  ......

  int32 surface_id() const { return surface_id_; }
  ......

 protected:
  ......

  int32 surface_id_;
  ......

};

      這個函式定義在檔案external/chromium_org/content/renderer/render_widget.h中。

      RenderWidget類的成員函式surface_id返回的是成員變數surface_id_的值,那麼這個成員變數是什麼時候初始化的呢?這也得從Browser程序的啟動過程說起。

      從前面的分析可以知道,Browser程序在啟動的過程中,會呼叫Native層的Shell類的靜態成員函式CreateNewWindow建立一個Shell視窗,如下所示:

void LaunchShell(JNIEnv* env, jclass clazz, jstring jurl) {
  ShellBrowserContext* browserContext =
      ShellContentBrowserClient::Get()->browser_context();
  GURL url(base::android::ConvertJavaStringToUTF8(env, jurl));
  Shell::CreateNewWindow(browserContext,
                         url,
                         NULL,
                         MSG_ROUTING_NONE,
                         gfx::Size());
}

       這個函式定義在檔案external/chromium_org/content/shell/android/shell_manager.cc中。

       Shell類的靜態成員函式CreateNewWindow在建立Shell視窗之前,會呼叫WebContents類的靜態成員函式Create建立一個WebContentsImpl物件,如下所示:
Shell* Shell::CreateNewWindow(BrowserContext* browser_context,
                              const GURL& url,
                              SiteInstance* site_instance,
                              int routing_id,
                              const gfx::Size& initial_size) {
  WebContents::CreateParams create_params(browser_context, site_instance);
  create_params.routing_id = routing_id;
  create_params.initial_size = AdjustWindowSize(initial_size);
  WebContents* web_contents = WebContents::Create(create_params);
  ......
}
      這個函式定義在檔案external/chromium_org/content/shell/browser/shell.cc中。

      注意引數routing_id的值等於MSG_ROUTING_NONE,它被封裝在一個WebContents::CreateParams物件傳遞給WebContents類的靜態成員函式Create。

      WebContents類的靜態成員函式Create的實現如下所示:

WebContents* WebContents::Create(const WebContents::CreateParams& params) {
  return WebContentsImpl::CreateWithOpener(
      params, static_cast<WebContentsImpl*>(params.opener));
}
      這個函式定義在檔案external/chromium_org/content/browser/web_contents/web_contents_impl.cc中。

      WebContents類的靜態成員函式Create呼叫WebContentsImpl類的靜態成員函式CreateWithOpener建立一個WebContentsImpl物件,後者的實現如下所示:

WebContentsImpl* WebContentsImpl::CreateWithOpener(
    const WebContents::CreateParams& params,
    WebContentsImpl* opener) {
  ......
  WebContentsImpl* new_contents = new WebContentsImpl(
      params.browser_context, params.opener_suppressed ? NULL : opener);

  ......

  new_contents->Init(params);
  return new_contents;
}
       這個函式定義在檔案external/chromium_org/content/browser/web_contents/web_contents_impl.cc中。

       WebContentsImpl類的靜態成員函式CreateWithOpener首先是建立一個WebContentsImpl物件,接著呼叫該WebContentsImpl物件的成員函式Init對其進行初始化,最後將該WebContentsImpl物件返回給呼叫者。 

       在呼叫WebContentsImpl類的成員函式Init對建立的WebContentsImpl物件進行初始化的時候,就會為Render端建立一個繪圖表面。在分析WebContentsImpl類的成員函式Init的實現之前,我們先分析WebContentsImpl物件的建立過程,即WebContentsImpl類的建構函式的實現,如下所示:

WebContentsImpl::WebContentsImpl(
    BrowserContext* browser_context,
    WebContentsImpl* opener)
    : ......,
      frame_tree_(new NavigatorImpl(&controller_, this),
                  this, this, this, this),
      ...... {
  ......
}
       這個函式定義在檔案external/chromium_org/content/browser/web_contents/web_contents_impl.cc中。

       WebContentsImpl類有一個型別為FrameTree的成員變數frame_tree_,用來管理網頁中的所有iframe標籤,也就是網頁的內容在第一級別上是按照frame來管理的,每一個frame又按照其內部的標籤進行第二級別上的組織。

       我們繼續分析WebContentsImpl類的成員變數frame_tree_描述的FrameTree物件的建立過程,即FrameTree類的建構函式的實現,如下所示:

FrameTree::FrameTree(Navigator* navigator,
                     RenderFrameHostDelegate* render_frame_delegate,
                     RenderViewHostDelegate* render_view_delegate,
                     RenderWidgetHostDelegate* render_widget_delegate,
                     RenderFrameHostManager::Delegate* manager_delegate)
    : ......,
      root_(new FrameTreeNode(this,
                              navigator,
                              render_frame_delegate,
                              render_view_delegate,
                              render_widget_delegate,
                              manager_delegate,
                              std::string())),
      ...... {
}
       這個函式定義在檔案external/chromium_org/content/browser/frame_host/frame_tree.cc中。

       FrameTree類有一個型別為scoped_ptr<FrameTreeNode>的成員變數root_,它指向的是一個FrameTreeNode物件,這個FrameTreeNode物件描述的是網頁的frame tree的根結點,它的建立過程如下所示:

FrameTreeNode::FrameTreeNode(FrameTree* frame_tree,
                             Navigator* navigator,
                             RenderFrameHostDelegate* render_frame_delegate,
                             RenderViewHostDelegate* render_view_delegate,
                             RenderWidgetHostDelegate* render_widget_delegate,
                             RenderFrameHostManager::Delegate* manager_delegate,
                             const std::string& name)
    : ......,
      render_manager_(this,
                      render_frame_delegate,
                      render_view_delegate,
                      render_widget_delegate,
                      manager_delegate),
      ...... {}
       這個函式定義在檔案external/chromium_org/content/browser/frame_host/frame_tree_node.cc中。

       FrameTreeNode類有一個型別為RenderFrameHostManager的成員變數render_manager_,它負責在Browser程序中建立一個RenderViewHostImpl物件與負責渲染網頁的Render程序進行通訊。

       從這裡我們可以知道,Browser程序為每一個網頁建立一個WebContentImpl物件,這個WebContentImpl物件將網頁看作是一個frame tree,這個frame tree由frame tree node組成,每一個frame tree node都包含有一個RenderFrameHostManager物件,這個RenderFrameHostManager物件負責與渲染網頁的Render程序進行通訊。通過這種frame tree的組織,Browser程序可以將一個網頁的不同frame放在不同的Render程序進行渲染,從而起到域隔離作用,保證安全性,因為不同的frame載入的一般是不同域的網頁。

       這一步執行完成後,回到WebContentsImpl類的靜態成員函式CreateWithOpener中,它接下來對前面建立的WebContentImpl物件進行初始化,這是通過呼叫WebContentImpl類的成員函式Init實現的,如下所示:

void WebContentsImpl::Init(const WebContents::CreateParams& params) {
  ......

  GetRenderManager()->Init(
      params.browser_context, params.site_instance, params.routing_id,
      params.main_frame_routing_id);

  ......

  if (browser_plugin_guest_) {
    ......
  } else {
    // Regular WebContentsView.
    view_.reset(CreateWebContentsView(
        this, delegate, &render_view_host_delegate_view_));
  }

  ......
}
       這個函式定義在檔案external/chromium_org/content/browser/web_contents/web_contents_impl.cc中。

       WebContentImpl類的成員函式Init首先呼叫另外一個成員函式GetRenderManager獲得一個RenderFrameHostManager物件,後者的實現如下所示:

RenderFrameHostManager* WebContentsImpl::GetRenderManager() const {
  return frame_tree_.root()->render_manager();
}
       這個函式定義在檔案external/chromium_org/content/browser/web_contents/web_contents_impl.cc中。

       從這裡可以看到,WebContentImpl類的成員函式GetRenderManager返回的是網頁的frame tree的根結點的RenderFrameHostManager物件,WebContentImpl類的成員函式Init接下來就呼叫它的成員函式Init繼續執行初始化工作。

       WebContentImpl類的成員函式Init呼叫RenderFrameHostManager類的成員函式Init執行初始化工作完畢之後,會判斷成員變數browser_plugin_guest_的值是否為NULL。當該成員變數的值不等於NULL的時候,表示當前正在處理的WebContentImpl物件是為一個Browser Plugin建立的。關於Browser Plugin,我們在前面Chromium的Plugin程序啟動過程分析一文中提涉及到,它的作用類似於iframe標籤。我們假設當前正在處理的WebContentImpl物件不是為一個Browser Plugin建立的,即這時候當前正在處理的WebContentImpl物件的成員變數browser_plugin_guest_的值是為NULL,那麼WebContentImpl類的成員函式Init接下來會呼叫另外一個函式CreateWebContentsView建立一個WebContentsViewAndroid物件,如下所示:

WebContentsView* CreateWebContentsView(
    WebContentsImpl* web_contents,
    WebContentsViewDelegate* delegate,
    RenderViewHostDelegateView** render_view_host_delegate_view) {
  WebContentsViewAndroid* rv = new WebContentsViewAndroid(
      web_contents, delegate);
  *render_view_host_delegate_view = rv;
  return rv;
}
      這個函式定義在檔案external/chromium_org/content/browser/web_contents/web_contents_view_android.cc中。

      函式CreateWebContentsView建立的WebContentsViewAndroid物件返回給WebContentsImpl類的成員函式Init之後,會儲存在成員變數view_中。

      回到WebContentsImpl類的成員函式Init中,前面提到,它會進一步呼叫獲得的一個RenderFrameHostManager物件的成員函式Init執行初始化工作,如下所示:

void RenderFrameHostManager::Init(BrowserContext* browser_context,
                                  SiteInstance* site_instance,
                                  int view_routing_id,
                                  int frame_routing_id) {
  ......

  SetRenderFrameHost(CreateRenderFrameHost(site_instance,
                                           view_routing_id,
                                           frame_routing_id,
                                           false,
                                           delegate_->IsHidden()));

  ......
}
       這個函式定義在檔案external/chromium_org/content/browser/frame_host/render_frame_host_manager.cc中。

       RenderFrameHostManager類的成員函式Init首先呼叫另外一個成員函式CreateRenderFrameHost建立一個RenderFrameHostImpl物件,用來在Browser程序中描述一個網頁的一個frame,接著又呼叫成員函式SetRenderFrameHost將創建出來的RenderFrameHostImpl物件儲存在WebContentImpl類的成員變數render_frame_host_中。

       接下來我們繼續分析RenderFrameHostManager類的成員函式CreateRenderFrameHost的實現,以便可以瞭解Render端的OpenGL上下文的繪圖表面的建立過程,如下所示:

scoped_ptr<RenderFrameHostImpl> RenderFrameHostManager::CreateRenderFrameHost(
    SiteInstance* site_instance,
    int view_routing_id,
    int frame_routing_id,
    bool swapped_out,
    bool hidden) {
  if (frame_routing_id == MSG_ROUTING_NONE)
    frame_routing_id = site_instance->GetProcess()->GetNextRoutingID();

  // Create a RVH for main frames, or find the existing one for subframes.
  FrameTree* fram