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