Chromium Graphics:3 Dコンテキストとその仮想化(Virtualization)-Part III


要旨:Part IとPart IIはそれぞれ実現原理の観点から3 Dコンテキストとレンダリング表面などの基本概念を紹介し、Chromiumが複数のコンテキストを作成する必要がある内部ロジックと、GPUデバイスのマルチコンテキストサポートの不足点を仮想コンテキストでどのように解決するかを述べた.Part IIIはChromiumソースコードがマルチコンテキストサポートとその仮想化をどのように抽象化しているかを深く解読する.
コンテキストとレンダリングサーフェスの抽象化と実装
まずChromiumがコンテキストを抽象化し、表面をレンダリングする方法を見てみましょう.
Chromiumは2つのC++抽象クラスを定義した:GLContextとGLSurface、それぞれOpenGLコンテキストとGLコマンドでレンダリングできる表面をカプセル化するために使用され、この2つはプラットフォームに関する詳細を隠し、すべての異なるプラットフォームでOpenGLコンテキストとレンダリング表面を操作するために統一的なインタフェース仕様を提供した.さらに、GLContextとGLSurfaceはbase::RefCountedテンプレートクラスから継承され、両方のインスタンスが参照カウントを有効にし、clientコードで安全に参照できることを示しています.
Chromiumは、Windows、Mac、Linux、Androidなどのシステムをサポートするプラットフォーム間システムであり、それに応じてChromiumは、X 11 Windowシステム用のGLContextGLXおよびGLSurfaceGLX、MacOS上のGLContextCGLおよびGLSurfaceCGL、仮想コンテキストGLContextVirtualなどのマルチプラットフォームをサポートするGLContextおよびGLSurfaceCGLの具体的な実装バージョンを提供し、GLContextも実現している.次の図は、GLContextとGLSurfaceのクラス階層図を示しています.
プラットフォーム関連のインプリメンテーションの詳細を非表示にするだけでなく、GLContextとGLSurfaceはコンテキストとレンダリング表面を作成するファクトリメソッドをそれぞれ定義し、プラットフォーム関連のソースコードファイル(gl_context_android.ccとgl_surface_android.cc.
GLContextのファクトリメソッドの作成
  static scoped_refptr<GLContext> CreateGLContext(
      GLShareGroup* share_group,
      GLSurface* compatible_surface,
      GpuPreference gpu_preference);

CreateGLContextは、与えられた3つのパラメータからGLContextインスタンスを作成します.
最初のパラメータshare_groupは新しいコンテキストの共有グループを指定し、空でなければ、この新しく作成されたコンテキストがこの共有グループに追加され、共有グループの他のコンテキストとリソースの共有が行われることを示します.
2番目のパラメータcompatible_surfaceは、GLContextインスタンスを初期化するためのレンダリングサーフェスを指定します.このレンダリングサーフェスの構成情報が作成するGLContextインスタンスと互換性がある場合にのみ、たとえばEGLの場合、互換性のあるレンダリングサーフェスはカラーバッファに同じタイプと深さを必要とします.そうしないと、コンテキストの作成に失敗し、関数は空に戻ります.
3番目のパラメータgpu_preferenceは、デュアルGPUシステムに対して、WebGLのコンテキストを作成するために独立したグラフィックスカードを使用する傾向があるなど、統合グラフィックスカードを使用するか、独立したグラフィックスカードを使用するかを指定します.現在、MacOSXプラットフォームだけがこの使用の好みに関心を持っている(MacProマシンは2 GPUデバイスの切り替えをサポートしているため、電力が不足している場合は集積グラフィックスカードを使用し、電力状況が良好な場合は独立グラフィックスカードを使用し、航続と性能表現のバランスが良い).
CreateGLContext実行プロセスは、プラットフォームGLImplementationに基づいてGLContextインスタンスを作成してからcompatible_surfaceはこのGLContextを初期化し、初期化に成功すればGLContextインスタンスを返すが、今後このGLContextインスタンスがこのGLSurfaceと現在の「コンテキスト」になる必要があるとは限らない.実際には互換性のあるGLSurfaceであれば、GLContextとMakeCurrentメソッドで現在のコンテキストになることができる.
GLSurfaceのファクトリメソッドの作成
static scoped_refptr<GLSurface> CreateViewGLSurface(gfx::AcceleratedWidget window);
static scoped_refptr<GLSurface> CreateOffscreenGLSurface(constgfx::Size& size);

1つ目は1つのスクリーン上(onscreen)で表面をレンダリングし、2つ目はスクリーン外レンダリング表面を作成することであることがわかります.これら2つのレンダリング面の違いについては、PartIとPart IIについて説明しています.CreateViewGLSurfaceは、ウィンドウシステムに関連するウィンドウIDを指定するAcceleratedWidgetのタイプのパラメータを受け入れます.AcceleratedWidgetタイプはtypedefによって定義され、各プラットフォームは異なります.例えば、Windowsプラットフォームでは、AcceleratedWidgetはウィンドウハンドルHWNDクラスであり、AndroidプラットフォームではAcceleratedWidgetはANativeWindowと定義されています.CreateOffscreenGLSurfaceでは、スクリーンレンダリング領域からのサイズ情報のみが必要ですが、これは必須ではありません.PbufferGLSurfaceEGLでは、sizeサイズがゼロでないことを確認すればよいだけです.sizeが0の場合、PbufferGLSurfaceEGLのコンストラクタでは、sizeサイズが自動的に(1,1)に設定され、一部のPbufferの実装でsizeが0の設定をサポートしないことを回避します.
GPUスレッドにおけるコンテキストとレンダリングサーフェスの作成
Part IIでは、ChromiumグラフィックススタックにおいてOpenGLコマンドの処理には、GPUクライアントがGLコマンドの実行要求を開始するC/Sモードが採用されており、GPUサービス側が要求に応答してグラフィックス駆動要求にGLコマンドを実行し、クライアントとサービス側がCommandBufferによりGLコマンドを送信および受信する.
Chromiumの実装コードから見ると、GPUクライアントとサービス側は非常に似た概念モデルを持っている.例えば、サービス側にはコンテキストや共有グループなどの抽象があり、クライアントにはコンテキスト(WebGraphicsContext 3 DImpl)や共有グループ(ShareGroup)などの概念もあり、一対一のGLコマンドマッピング(GLES 2 Implementation)もある.すなわちOpenGLコマンドはGLES 2 Implementationクラスにおいてクライアントのカプセル化を提供しており,CommandBufferによりGLコマンドをGPUサービス側に送信することができる.
GPUスレッドコンテキストとレンダリング面の作成時にGPUクライアントによって駆動されます.GPUクライアントがGLコマンドの実行を要求する前に、メッセージGpuCommandBufferMsg_を送信する必要があるInitializeは、GPUサービス側に対応するコンテキストおよびレンダリングサーフェスを作成するように要求する.GPUスレッド受信GpuCommandBufferMsg_Initializeメッセージの後、メッセージ処理方法GpuCommandBufferStub::OnInitialize GLContextとGLSurfaceを作成して初期化します.
Chromiumは、GpuCommandBufferStubのコンストラクション関数で、仮想コンテキストを作成する必要があるworkaroundが現在のContextGroupのフィーチャー情報に含まれているかどうかを確認し、GLContextを作成して仮想コンテキストインスタンスを作成する必要があるかどうかを決定します.
  use_virtualized_gl_context_ |=
      context_group_->feature_info()->workarounds().use_virtualized_gl_contexts;

リアルコンテキストの作成
use_の場合virtualized_gl_context_値はfalseで、GpuCommandBufferStubを初期化するときに仮想コンテキストを作成するコードロジックをスキップし、GLContext::CreateGLContextを直接呼び出して実際のコンテキストを作成し、GLContext::MakeCurrent設定"現在"のコンテキストをすぐに呼び出します.
  if (!context.get()) {
    context = gfx::GLContext::CreateGLContext(
        channel_->share_group(), surface_.get(), gpu_preference_);
  }
  if (!context.get()) {
    DLOG(ERROR) << "Failed to create context.";
    OnInitializeFailed(reply_message);
    return;
  }

  if (!context->MakeCurrent(surface_.get())) {
    LOG(ERROR) << "Failed to make context current.";
    OnInitializeFailed(reply_message);
    return;
  }

仮想コンテキストの作成と初期化
仮想コンテキストはGLContextVirtualクラスにカプセル化され、GLContextにも継承されているが、他のContextとは異なり、第1に、GLContextVirtualの実装はgpu/command_一般的なui/glディレクトリではなくbuffer/serviceディレクトリです.これは、GLContextVirtualがグラフィックスタック固有の抽象であることを示しています.第二に、GLContextVirtualインスタンスを構築するために必要なパラメータが異なり、実際のコンテキストを伝える必要がある以外は、また、この仮想コンテキストに関連付けられたGLコマンドデコーダ(GLES 2 Decoder)を呼び出してGL状態の保存および復元を実行する必要がある.GLContextVirtualコンストラクタ定義宣言は次のとおりです.
  GLContextVirtual(
      gfx::GLShareGroup* share_group,
      gfx::GLContext* shared_context,
      base::WeakPtr<gles2::GLES2Decoder> decoder);

最初のパラメータshare_groupはそのベースクラスGLContextを構築するために使用され、2番目のパラメータはこの仮想コンテキストが共有するリアルコンテキストであり、3番目のパラメータdecoderはGPUサービス側がGPUコマンドバッファを復号するGLコマンドであり、GLContextVirtualは新しいOpenGLステータスリカバリ器を構築する必要がある.
GpuCommandBufferStub::OnInitializeを呼び出す場合、use_virtualized_gl_context_値がTRUEの場合、GpuCommandBufferStubの仮想コンテキストを作成する必要があります.複数の仮想コンテキストが同じリアルコンテキストを共有しているため、このリアルコンテキストが作成されていることを保証する必要があります.したがって、Chromiumはまず、GpuChannelの共有グループから共有コンテキストインスタンスを取得し、新しい仮想コンテキストを作成しようとします.共有された実際のコンテキストがまだ作成されていないことが判明した場合は、GLContext::CreateGLContextを呼び出します.この共有された実際のコンテキストに基づいて、新しい仮想コンテキストが構築され、作成されたレンダリングサーフェスで初期化されます.
//   GPU                  GPUChannel      ,               。
if (use_virtualized_gl_context_ && channel_->share_group()) {
    context = channel_->share_group()->GetSharedContext();
    if (!context.get()) {
      //               ,   CreateGLContext   
      context = gfx::GLContext::CreateGLContext(
          channel_->share_group(),
          channel_->gpu_channel_manager()->GetDefaultOffscreenSurface(),
          gpu_preference_);
      if (!context.get()) {
        DLOG(ERROR) << "Failed to create shared context for virtualization.";
        OnInitializeFailed(reply_message);
        return;
      }
      //            ,       Context           
      channel_->share_group()->SetSharedContext(context.get());
    }
    // This should be a non-virtual GL context.
    DCHECK(context->GetHandle());
    //    GpuCommandBufferStub           ,    
    context = new gpu::GLContextVirtual(
        channel_->share_group(), context.get(), decoder_->AsWeakPtr());
    if (!context->Initialize(surface_.get(), gpu_preference_)) {
      // TODO(sievers): The real context created above for the default
      // offscreen surface might not be compatible with this surface.
      // Need to adjust at least GLX to be able to create the initial context
      // with a config that is compatible with onscreen and offscreen surfaces.
      context = NULL;

      DLOG(ERROR) << "Failed to initialize virtual GL context.";
      OnInitializeFailed(reply_message);
      return;
    }

スレッドプライベートコンテキストとレンダリングサーフェス
任意のスレッドは、同じ時点で1つの現在のコンテキストのみをバインドできます.つまり、1つの現在のレンダーサーフェスのみを意味します.GLContextとGLSurfaceは、2つのスレッドに関連するプライベートデータcurrent_を変更するためのSetCurrentメソッドを提供します.context_およびcurrent_surface_変数:
void GLContext::SetCurrent(GLSurface* surface) {
  current_context_.Pointer()->Set(surface ? this : NULL);
  GLSurface::SetCurrent(surface);
  …
}
void GLSurface::SetCurrent(GLSurface* surface) {
  current_surface_.Pointer()->Set(surface);
}

この2つの変数は、仮想コンテキストの切り替えに補助情報を提供し、仮想コンテキストを切り替えると、現在のスレッドに格納されているcurrent_に基づいてcontext_およびcurrent_surface_の値は、コンテキストとレンダーサーフェスが変更されたかどうかを判断し、実際のコンテキストを切り替えるかどうかを決定します.
仮想コンテキストの切り替え
実際のコンテキストの切り替えGLContext::MakeCurrentが指定したレンダリング面とともにスレッドとなる「現在」コンテキストを呼び出すことにより、MakeCurrentはプラットフォームに関連するAPIを呼び出してeglMakeCurrentやglXmakeCurrentなどの「現在」コンテキストを切り替える.
一方、仮想コンテキストの切り替えは軽量であり、最初の呼び出しMakeCurrentが実際のコンテキストに対して「現在」コンテキストの切り替えを要求する場合を除き、他の呼び出しはコンテキストの切り替えをトリガーしません.GLContextVirtual::MakeCurrentはVirtualGLApi::MakeCurrentにコンテキスト切り替えの操作を要求します.次のコードを参照してください(重要な部分にはコメントがあります).
bool VirtualGLApi::MakeCurrent(GLContext* virtual_context, GLSurface* surface) {
  //                      (VirtualGLApi  GLContextVirtual        );
  bool switched_contexts = g_current_gl_context_tls->Get() != this;
  //            ;
  GLSurface* current_surface = GLSurface::GetCurrent();
  //                          
  if (switched_contexts || surface != current_surface) {
    // MakeCurrent 'lite' path that avoids potentially expensive MakeCurrent()
    // calls if the GLSurface uses the same underlying surface or renders to
    // an FBO.
    if (switched_contexts || !current_surface ||
        !virtual_context->IsCurrent(surface)) {
      //        MakeCurrent  
      if (!real_context_->MakeCurrent(surface)) {
        return false;
      }
    }
  }

  DCHECK_EQ(real_context_, GLContext::GetRealCurrent());
  DCHECK(real_context_->IsCurrent(NULL));
  DCHECK(virtual_context->IsCurrent(surface));

  //              ,      GLES2Decorder       。
  if (switched_contexts || virtual_context != current_context_) {
    // There should be no errors from the previous context leaking into the
    // new context.
    DCHECK_EQ(glGetErrorFn(), static_cast<GLenum>(GL_NO_ERROR));

    // Set all state that is different from the real state
    GLApi* temp = GetCurrentGLApi();
    SetGLToRealGLApi();
    if (virtual_context->GetGLStateRestorer()->IsInitialized()) {
      //           GLES2Decoder   
      virtual_context->GetGLStateRestorer()->RestoreState(
          (current_context_ && !switched_contexts)
              ? current_context_->GetGLStateRestorer()
              : NULL);
    }
    SetGLApi(temp);
    current_context_ = virtual_context;
  }
  SetGLApi(this);
  //          
  virtual_context->SetCurrent(surface);
  if (!surface->OnMakeCurrent(virtual_context)) {
    LOG(ERROR) << "Could not make GLSurface current.";
    return false;
  }
  return true;
}

まとめ
これまで,Chromiumでマルチコンテキストサポートが必要とされる原理と方法,およびコンテキストを仮想化する理由について詳細に述べた.簡単に言えば、Chromiumは、基本ページやWebGLなどの異なるソースからのGPUアクセラレータシーンを処理する必要があるため、異なる3 Dコンテキストが必要であり、ChromiumのすべてのGPU操作は同じスレッド上で発生し、複数のコンテキスト間で頻繁に切り替える必要がある可能性があるが、現在、一部のGPUデバイスは複数のコンテキストのサポートに対して多かれ少なかれいくつかの問題がある.仮想化コンテキストの導入は、GPUデバイスが複数のコンテキストをうまくサポートできないという問題を解決することであり、仮想化されたコンテキストによって、複数の仮想コンテキストが実際のコンテキストを共有できるようにすることであり、GPUデバイスにとっては、1つのコンテキストの状況を処理するだけであり、Chromiumにとって、仮想コンテキスト間の切り替えは実際のコンテキストの切り替えを招くことはない.GL状態を復元するだけでよい.