ホーム
» WebGL の最適化
Web セキュリティを確保するために WebGL が課す必要のある追加の検証により、WebGL アプリケーションの CPU 側のオーバーヘッドは、ネイティブ OpenGL アプリケーションと比較して高くなることが知られています。このため、グラフィックを多用するアプリケーションを移植すると、GL 関数とのインターフェイス時に CPU 側でボトルネックになる可能性があります。したがって、最高のパフォーマンスを得るためには、WebGL を多用することが予想されるアプリケーションで GL API の使用をプロファイリングして最適化するために特別な注意を払う必要があります。この最適化ガイドでは、WebGL パフォーマンスの向上に役立つことが判明しているさまざまなテクニックに焦点を当てます。
非常に多くの GL ハードウェアおよびドライバベンダー、そしてオペレーティングシステムの組み合わせが存在するため、特定の最適化ガイドを生成することは困難です。特定のハードウェア/ドライバ/OS の組み合わせで効率的な最適化が、別のベンダーのドライバではそれほど違いがないことがわかっています。幸いなことに、あるドライバに対する特定の最適化が、別の GPU ベンダーのハードウェアでパフォーマンスが低下するような矛盾したシナリオを見つけることは比較的まれです。多くの場合、これは、特定のハードウェアでサポートされていない特定の機能が原因であり、ドライバがエミュレーションに頼らざるを得ないことが原因です。たとえば、あるケースでは、ネイティブ GL ドライバが ETC2 圧縮テクスチャ形式のサポートを宣伝しているにもかかわらず、グラフィックハードウェアがこれを実装しておらず、別のケースでは、頂点シェーダープリミティブリスタートインデックスを使用すると、GL ドライバがソフトウェアで頂点シェーダーを実行するようにフォールバックすることがわかりました。残念ながら、OpenGL 仕様には、ドライバがこれらのタイプのパフォーマンス上の注意点を報告する手段が提供されていないため、GL を最適化する場合は、さまざまなターゲットハードウェア間でベンチマークを実行できることがほぼ必要です。また、実行時に Web ページのコンソールに注意を払うことも役立ちます。ブラウザはコンソールログに追加のパフォーマンス警告を報告できるからです。
また、検出されたパフォーマンスの問題の一部は、ブラウザとその利用ソフトウェアライブラリの非効率性または完全なパフォーマンスバグが原因であり、基盤となる GL ドライバや Web セキュリティ全般とは関係がないことも認識する必要があります。最初に Emscripten ポート GL コードベースの最適化に取り組んでいたとき、ほとんどのブラウザは WebGL スタックに非効率であることがわかりましたが、これは予想どおりでした。Emscripten と asm.js の前は、ネイティブと Web 間でこのような正確な GL パフォーマンス比較を実行することすらできなかったため、多数のパフォーマンスクリティカルな問題が欠落していました。多くの人が大規模な Emscripten コードベースで WebGL に負荷をかけるにつれて、この側面は着実に改善されているため、このガイドの項目の一部は将来は関連性がなくなる可能性があります。将来、このガイドを読んでいるときに、すべての場合において純損失のように思われるものを見つけた場合は、議論するために doc PR を提出してください。同様に、特定の GL アクセスパターンが Web でネイティブと比較して桁違いに遅い場合は、パフォーマンスバグである可能性があります。
次の最適化のヒントには、実際に影響を与えることが知られているさまざまな状況がリストされていますが、最適化を盲目的に行うのではなく、実験中はプロファイラーを手元に置いておくことをお勧めします。
Emscripten では、さまざまな OpenGL および OpenGL ES API フレーバーをさまざまなリンカフラグでターゲットにできます。
デフォルトでは、特別な GL 関連のリンカフラグが選択されていない場合、Emscripten は WebGL 1 API をターゲットにします。ユーザーコードは、C/C++ コードに OpenGL ES 2.0 ヘッダーを含めることでアクセスします(#include <GLES2/gl2.h>
および #include <GLES2/gl2ext.h>
)。このモードは GLES 2 のように機能しますが、いくつかの WebGL 固有の変更と制限が適用される点が異なります。WebGL 1 と OpenGL ES 2 の違いについてほぼ完全な参照については、WebGL 1 仕様:WebGL と OpenGL ES 2.0 の違い を参照してください。
アプリケーションがクライアント側のメモリからジオメトリをレンダリングする場合は、リンカフラグ -sFULL_ES2
を使用してビルドする必要があります。このモードは、新しいコードベースの移植を容易にするのに便利ですが、WebGL 自体はクライアント側のメモリからのレンダリングをサポートしていないため、この機能はエミュレートされます。最高のパフォーマンスを得るには、代わりに VBO を使用し、-sFULL_ES2
リンカフラグなしでビルドします。
アプリケーションが古いデスクトップ OpenGL API をターゲットにしている場合は、-sLEGACY_GL_EMULATION
フラグを使用してビルドすると機能する可能性があります。ただし、このモードでビルドする場合、動作したとしても、パフォーマンスを期待しないでください。アプリケーションがこのモードで遅く、固定パイプラインのみを使用し、シェーダーをまったく使用しない場合は、-sLEGACY_GL_EMULATION
と -sGL_FFP_ONLY
リンカフラグを組み合わせて、パフォーマンスをいくらか回復することもできます。ただし、一般的には、アプリケーションを WebGL 1/OpenGL ES 2 を使用するように移植するのに努力を費やすことをお勧めします。
OpenGL ES 3 をターゲットにする場合、クライアント側のメモリからレンダリングする必要がある場合、または glMapBuffer*()
API の使用が必要な場合は、リンカフラグ -sFULL_ES3
を渡してこれらの機能をエミュレートします。コア WebGL 2 にはありません。このエミュレーションはパフォーマンスを低下させると予想されるため、代わりに VBO を使用することをお勧めします。
アプリケーションが WebGL 2/OpenGL ES 3 機能を必要としない場合でも、アプリケーションを WebGL 2 で実行するように移植することを検討してください。WebGL 2 の JavaScript 側のパフォーマンスは一時的なガベージを生成しないように最適化されており、これにより 3〜7% の速度向上が得られ、レンダリング時の潜在的な途切れを減らすことが観察されています。これらの最適化を有効にするには、リンカフラグ -sMAX_WEBGL_VERSION=2
を使用してビルドし、GL の起動時に WebGL 2 コンテキストを作成してください(EGL を使用する場合は OpenGL ES 3 コンテキスト)。
GL パフォーマンスの測定には、さまざまなツールを使用できます。一般的に、ここでは開発者が Web ブラウザ固有のプロファイリングツールのみに焦点を当てることを制限しないことを推奨しますが、実際には、ネイティブプロファイラーも同様に、それ以上によく機能することがわかっています。ネイティブプロファイラーを使用する場合の唯一の欠点は、ブラウザで WebGL がどのように実装されているかについて詳細な知識が重要になる場合や、そうでない場合は GPU に送信されるコールストリームを理解するのが難しい場合があることです。
異なる WebGL エントリポイントでどれだけの時間が費やされているかを確認するには、Firefox を Gecko Profiler アドオン とともに使用します。このプロファイラツールは、手書きの JavaScript、asm.js/WebAssembly、ネイティブ Firefox C/C++ ブラウザコードなど、実行されたコードのスタック全体でタイミングデータを表示できるため、他のプロファイリングツールと比較して非常に価値があります。
CPUのオーバーヘッドをプロファイリングするのに役立つネイティブツールとしては、AMD CodeXL、Intel VTune Amplifier、macOS Instrumentsなどがあります。ブラウザのパフォーマンスプロファイリングツールで、ブラウザのエントリーポイント自体に多くの時間が費やされていることが示唆される場合、これらのツールはホットスポットを特定するのに役立ちます。ただし、ネイティブCPUプロファイリングツールを使用する場合は、シンボル情報データ(Windowsの.pdbファイルなど)を取得するために、ブラウザコードをソースから手動でビルドする必要があります。これらのツールはローカルで検索します。このようにしてFirefoxをデバッグする場合、Firefoxのマルチプロセスアーキテクチャを無効にすると、コンテンツプロセスをブラウザ自体と同じスレッドで実行するトレースを取得するのに役立ちます。Firefoxブラウザをabout:config
ページに移動し、設定browser.tabs.remote.autostart.2
をfalse
に設定して、ブラウザを再起動します。
GPU側のAPI呼び出しトレースをデバッグするには、NVidia Nsight、Intel Graphics Performance Analyzers、Visual Studio Graphics Debugger、AMD CodeXLが便利なツールとなります。Windowsでは、FirefoxはWebGLコンテンツをレンダリングするためにOpenGLまたはDirect3Dを使用できます。Direct3Dがデフォルトですが、例えばAMD CodeXLはOpenGLの呼び出しストリームのみをトレースします。FirefoxでWebGL API呼び出しをトレースするためにAMD CodeXLを使用するには、ブラウザをabout:config
に移動し、設定webgl.disable-angle
をtrue
に設定してページをリロードします。
WebGLでは、すべてのGL関数呼び出しには、たとえ単純に見えてほとんど何もしていないように見えるものでも、ある程度のオーバーヘッドがあります。これは、WebGL実装が各呼び出しを検証する必要があるためです。基盤となるネイティブOpenGL仕様では、Web上で信頼できるセキュリティに関する保証が提供されていないためです。さらに、asm.js/WebAssembly側では、各WebGL呼び出しでFFIトランジション(asm.jsコンテキストで実行中のコードとブラウザのネイティブC++コンテキストで実行中のコード間のジャンプ)が生成されます。これは、asm.js/WebAssembly内の通常の関数呼び出しよりもわずかに高いオーバーヘッドがあります。したがって、Web上では、WebGLへの呼び出し数を最小限に抑えることが、一般的にCPU側のパフォーマンスにとって最善です。以下のヒントを適用できます。
冗長な呼び出しを避けるために、高レベルでレンダラーと入力アセットを最適化します。必要に応じて設計をリファクタリングし、レンダラーがどのような状態の変更が関連性があり、どれが必要ないかをより適切に推論できるようにします。最高の種類のキャッシュは不要なキャッシュであるため、高レベルのレンダラーがGL呼び出しストリームを無駄なく保つことができれば、最速の結果が得られます。ただし、達成が難しい場合、以下で説明するように、いくつかの種類の低レベルキャッシュが効果的になることがあります。
レンダラーコード内でGL状態をキャッシュし、状態が変わっていない場合は、同じ状態を複数回設定する冗長な呼び出しを避けます。たとえば、一部のエンジンでは、各描画呼び出しの前に深度テストまたはアルファブレンディングモードを盲目的に再構成したり、各呼び出しごとにシェーダープログラムをリセットしたりする場合があります。
特定の操作後、GLを特定の「グラウンド状態」にリセットするすべてのタイプのレンダラーパターンを避けます。よく見られるのは、各描画呼び出しを発行した後、既知の固定構成に戻るために、glBindBuffer(GL_ARRAY_BUFFER, 0)
、glUseProgram(0)
、またはfor(i in 0 -> max_attributes) glDisableVertexAttribArray(i);
を呼び出すことです。代わりに、ある描画呼び出しから別の描画呼び出しに移行するときに必要なGL状態のみを遅延的に変更します。
GL状態を有効にする必要がある場合にのみ、遅延的に設定することを検討してください。たとえば、次の呼び出しストリームでは
// First draw glBindBuffer(...); glVertexAttribPointer(...); glActiveTexture(0); glBindTexture(GL_TEXTURE_2D, texture1); glActiveTexture(1); glBindTexture(GL_TEXTURE_2D, texture2); glDrawArrays(...); // Second draw (back-to-back) glBindBuffer(...); glVertexAttribPointer(...); glActiveTexture(0); // (*) glBindTexture(GL_TEXTURE_2D, texture1); // (*) glActiveTexture(1); // (*) glBindTexture(GL_TEXTURE_2D, texture2); // (*) glDrawArrays(...);
星印が付いている4つのAPI呼び出しはすべて冗長ですが、単純な状態キャッシュだけではこれを検出するには不十分です。より遅延的な状態キャッシュメカニズムは、これらのタイプの変更を検出できます。ただし、深く遅延的な状態キャッシュを実装する場合は、最適化を動機付けるためのプロファイリングデータがある場合にのみ行うことをお勧めします。レンダリング前にすべてのGL状態に遅延キャッシュ技術を適用すると、他の理由でもコストがかかる可能性があり、レンダラーがすでに冗長な呼び出しの再送信を回避するのが得意な場合、パフォーマンスが無駄になる可能性があります。適切な量のキャッシュには、バランスを見つけるために少しの調整が必要になる場合があります。
経験則としては、高レベルの設計によって、そもそも冗長な状態呼び出しを回避するレンダラーの方が、低レベルでの状態キャッシュに大きく依存するレンダラーよりも、一般的に効率的です。
完全に冗長なAPI呼び出しを削除することに加えて、他のテクニックを使用して状態の変更を最小限に抑える方法にも注意を払うことが重要です。次のチェックリストは、いくつかの可能性を提供します。
オフスクリーンレンダーターゲットにレンダリングする場合は、複数のFBOを使用し、レンダーターゲットの切り替えに1回のglBindFramebuffer()呼び出しのみを必要とするようにします。これにより、フレームごとに複数の呼び出しを行ってFBO状態を設定する必要がなくなります。
FBO状態を変更することは避け、状態が変化しない複数のイミュータブル/静的なFBOを設定することを推奨します。FBO状態を変更すると、ブラウザでそのFBOの組み合わせの再検証が行われますが、イミュータブルなFBOは作成時に1回だけ検証する必要があります。
レンダリングのために頂点属性を設定するために、いくつかのGL関数を呼び出す必要を避けるために、可能な限りVAOを使用します。
glUniform4f()
を複数回呼び出して各ユニフォームを個別に更新する代わりに、ユニフォームの配列に対するglUniform*呼び出しをまとめて、1つのglUniform4fv()
配列呼び出しで更新します。または、WebGL 2では、さらに良い方法として、Uniform Buffer Objectsを使用します。
レンダリング時にglGetUniformLocation()
を呼び出すのではなく、起動時にシェーダープログラムごとに1回ロケーションをクエリし、それらをキャッシュします。
該当する場合は常に、インスタンス化レンダリングを使用します。
より良いジオメトリバッチ処理とインスタンス化の機会を可能にするために、複数のテクスチャを1つのテクスチャにアトラス化することを検討してください。
ネイティブGLプラットフォームと比較して、レンダリング可能なものをより積極的にカリングすることを検討してください(可能な限り厳密な場合はすでに不要)。
効率的なGPU使用の最も重要な側面は、レンダリング中にCPUがGPUでブロックする必要がなく、逆もまた然りであることを確認することです。これらの種類のストールは、非常にコストのかかるCPU-GPU同期点を生成し、両方のリソースの利用率が低下します。一般的に、このようなシナリオが発生している兆候は、全体的なGPUとCPUの使用率を観察することで検出できます。GPUプロファイラーが、GPUが多くの時間アイドル状態であると主張しているが、CPUプロファイラーが、CPUが順番にアイドル状態である、または特定のGL関数の完了に非常に長い時間がかかると主張している場合は、フレームがGPUに効率的に送信されていないが、描画呼び出しの送信中にGPU-CPU同期が発生していることを示唆しています。残念ながら、OpenGL仕様では、どのGL呼び出しがストールを引き起こす可能性があるかについて、パフォーマンス保証は提供されていません。したがって、次の動作に注意し、これらを変更して効果を再プロファイリングすることで実験してください。
レンダリング時に新しいGLリソースを作成することは避けます。これは、レンダリング時の
glGen*()
関数とglCreate*()
関数(glGenTextures()
、glGenBuffers()
、glCreateShader()
など)への呼び出しを最適化することを意味します。新しいリソースが必要な場合は、それらを使用してレンダリングを試みる数フレーム前に、それらを作成してアップロードしてみてください。同様に、レンダリングされたばかりのGLリソースを削除しないでください。関数
glDelete*()
は、ドライバーがリソースのいずれかが使用中であることを検出した場合、パイプラインの完全なフラッシュを導入する可能性があります。リソースを削除するのはロード時のみにするのが最善です。レンダリング時に
glGetError()
またはglCheckFramebufferStatus()
を呼び出さないでください。これらの関数は両方ともパイプラインの完全な同期を実行できるため、ロード時にのみチェックするように制限する必要があります。同様に、レンダリング時に
glGet*()
API関数を呼び出さないでください。代わりに、起動時とロード時にそれらをクエリし、レンダリング時にキャッシュされた結果を参照してください。レンダリング時にシェーダーをコンパイルすることは避けてください。これは、
glCompileShader()
とglLinkProgram()
の両方が非常に遅くなる可能性があるためです。レンダリング時に
glReadPixels()
を呼び出して、テクスチャの内容をメインメモリにコピーしないでください。必要な場合は、代わりにWebGL 2のGL_PIXEL_PACK_BUFFER
バインディングターゲットを使用して、最初にGPUサーフェスをオフスクリーンターゲットにコピーし、後でそのサーフェスの内容をglReadPixels()
でメインメモリにコピーします。
CPUとGPUの間でメモリを転送することは、GLパフォーマンス問題の一般的な原因です。これは、新しいGLリソースの作成が遅くなる可能性があり、データが準備できていない場合、または古いバージョンのデータが新しいバージョンで上書きされる前にまだ必要な場合、データのアップロードまたはダウンロードによってCPUがブロックされる可能性があるためです。
平面属性を含む複数のVBOよりも、単一のVBOにインターリーブされた頂点データを優先します。これにより、GPU頂点キャッシュの動作が改善され、レンダリングのために頂点属性ポインターを設定する際の複数の冗長なglBindBuffer()
呼び出しが回避されます。
実行時にバッファやテクスチャの内容をリサイズするために、glBufferData()
や glTexImage2D/3D()
を呼び出すことは避けてください。動的なVBOのサイズを増減させる場合は、フレームごとにリサイズする必要がないように、std::vectorスタイルの幾何学的配列拡張のセマンティクスを使用してください。
バッファテクスチャデータを更新する場合は、テクスチャやバッファの内容全体が変更される場合でも、glBufferSubData()
および glTexSubImage2D/3D()
を呼び出すことを推奨します。バッファのサイズが縮小する場合は、ストレージをすぐに再作成せずに、余分なサイズは無視してください。
動的な頂点バッファデータについては、使用中のVBOをアップロードしないように、各フレームでVBOのダブルバッファリングまたはトリプルバッファリングを検討してください。GL_STREAM
よりもGL_DYNAMIC
の頂点バッファを使用することを推奨します。
CPU-GPUパイプラインの同期バブルが発生していないことを確認し、レンダリングが依然としてGPUにバインドされている場合、以下の最適化が役立つ可能性があります。
フォワードライティングレンダラーでのジオメトリの複数加算ライティング描画パスは、実装が簡単ですが、これが生成するGL API呼び出しの量が多すぎてコストがかかる可能性があります。そのような場合は、一部のオブジェクトが特定のライトの影響を受けない場合にシェーダーで無操作の算術演算が作成されるとしても、1つのシェーダーパスで複数のライトの寄与を計算することを検討してください。
十分な場合は、可能な限り低いフラグメントシェーダー精度 (lowp) を使用してください。GPU GLSLドライバーが実行時に最適化を行うことを期待せずに、オフライン作成時にシェーダーを事前に積極的に最適化してください。これはモバイルGPUドライバーにとって特に重要です。
プログラムがCPUにバインドされているかGPUにバインドされているかに応じて、ターゲットFBO、次にシェーダープログラム、そして他の必要なGL状態の変更を最小限に抑えるか、オーバー描画を最小限に抑えるためにレンダラブルをソートします。これはタイルベースのレンダラーに役立ちます。FBOの内容が不要になった場合は、WebGL 2 glDiscardFramebuffer()
を呼び出してください。
GPUプロファイラーを使用するか、レンダリングされたシーンにどの程度のオーバー描画があるかをプロファイリングするのに役立つカスタムフラグメントシェーダーを実装します。大量のオーバー描画は、余分な作業を生成するだけでなく、同じディスプレイメモリのブロックへのレンダリング間の連続的な依存関係が並列レンダリングを遅くします。深度バッファリングが有効になっている3Dシーンをレンダリングする場合は、オーバー描画と冗長なピクセル単位の塗りつぶし帯域幅を最小限に抑えるために、シーンを前面から背面へソートすることを検討してください。3Dシーンで非常に複雑なフラグメントシェーダーを使用する場合は、実際にラスタライズされたカラーフラグメントの数を最小限に抑えるために、深度プレパスを実行することを検討してください。
最後に、その他多くの最適化が効果的であることが証明されています。
Web上では、一般にどの圧縮テクスチャ形式が利用可能になるかを予測することはできません。過剰なダウンロードを最小限に抑えるために、テクスチャを複数の圧縮テクスチャバンドル(例:形式ごとに1つ)にオーサリングし、実行時に適切なものをダウンロードします。テクスチャやその他のアセットをIndexedDBに保存して、後続の実行時に再ダウンロードする必要がないようにします。Emscriptenリンカーフラグ -sGL_PREINITIALIZED_CONTEXT
は、そのようなテクスチャ形式のチェックを事前に行うhtmlシェルページのオーサリングに役立ちます。
他のアセットがダウンロードされているときと並行してシェーダーをコンパイルすることを検討してください。これにより、シェーダーのコンパイル時間が遅くなるのを隠すことができます。
大量のアセットをダウンロードする前に、ページロードプロセスの早い段階で、ユーザーのブラウザでWebGLサポートをテストします。ユーザーは、数メガバイトのアセットをダウンロードするのを待つ必要があり、その後、待機後にWebGLが利用できないというエラーメッセージが表示されると、不満を感じることがあります。
WebGL初期化が失敗した場合は、"webglcontextcreationerror"
コールバックを使用して、WebGLコンテキストエラーの理由を確認してください。ブラウザは、コンテキスト作成エラーハンドラーで、根本原因を診断するための優れた診断を提供できます。
キャンバスの表示サイズ(DOM要素のCSSピクセルサイズ)と、キャンバスで初期化されたWebGLコンテキストの物理レンダリングターゲットサイズに細心の注意を払い、これら2つが一致して1:1ピクセルパーフェクトコンテンツをレンダリングするようにしてください。
failIfMajorPerformanceCaveat
フラグを使用してコンテキスト作成をプローブし、ソフトウェアでのレンダリングを検出した場合に、そのような場合にグラフィックの忠実度を下げます。
WebGLコンテキストを、必要な最小限の機能のみで初期化するようにしてください。WebGLコンテキスト作成パラメーターには、アルファ、深度、ステンシル、MSAAのサポートが含まれており、ほとんどの場合、たとえば、HTMLページの背景に対してキャンバスをアルファブレンディングするサポートは必要なく、無効にする必要があります。
*glGetProcAddress()
API関数を一切使用しないでください。Emscriptenは、すべてのWebGL拡張機能を含め、すべてのGL API関数への静的リンクを提供します。*glGetProcAddress()
APIは、既存のコードの移植を容易にするための互換性のためだけに提供されますが、動的に取得した関数ポインターを介してWebGLにアクセスすると、asm.js / WebAssemblyで動的ディスパッチが実行する必要がある余分な関数ポインターセキュリティ検証のために、直接関数呼び出しよりも著しく遅くなります。EmscriptenはすべてのGLエントリポイントを静的にリンクして提供するため、最高のパフォーマンスを得るためにこれを活用することをお勧めします。
setTimeout()
APIの代わりに、アニメーションをレンダリングするには、常に requestAnimationFrame()
ループを使用してください。これにより、アニメーションティックで最もスムーズなスケジューリングが得られます。
WebGL 1と比較して、新しいWebGL 2 APIは、WebGL 2をターゲットにするだけでアクティブになる、本質的に無料のAPI最適化を提供します。この高速化は、WebGL 2 APIがJavaScriptバインディングの観点から改訂されたという事実に由来し、JSガベージコレクターの負荷を増大させる一時オブジェクトを割り当てる必要なしにWebGLを使用することが可能になりました。これらの新しいエントリポイントは、asm.jsおよびWebAssemblyアプリケーションとうまく適合し、WebGL APIを少し使いやすくします。ケーススタディとして、Unreal Engine 4をWebGL 2をターゲットにするように更新したところ、他のエンジンを修正することなく、スループットパフォーマンスが7%向上しました。
この無料のパフォーマンスのソースがあるため、パフォーマンスが懸念される場合は、他のWebGL 2機能が必要ない場合でも、すべての開発者がWebGL 2をターゲットに移行することを強くお勧めします。WebGL 2は、Firefox 51およびChrome 58以降で利用可能です(#4945を参照)。また、caniuse: WebGL 2テーブルも参照してください。少し注意すれば、WebGL 1とWebGL 2の両方のAPIを同時にターゲットにし、可能な場合は最高のパフォーマンスを活用できますが、互換性の低いGPUには正常にフォールバックできます。
これらの2つの仕様を使用する場合は、WebGL 1 は OpenGL ES 2.0 仕様に基づいており、WebGL 2 は OpenGL ES 3.0 仕様に基づいていることを覚えておくとよいでしょう。
WebGL 2への移行は、OpenGL ESと同様に、WebGLが下位互換性のあるAPIではないという事実によってわずかに複雑になっています。つまり、WebGL 1 / OpenGL ES 2アプリケーションは、一般的に、WebGL 2 / OpenGL ES 3.0で実行するために、新しいバージョンのGLコンテキストを初期化するだけでは動作しません。これは、2つのバージョン間で多くの下位互換性を損なう変更が導入されたためです。ただし、これらの変更は機能的というよりは表面的/装飾的であり、機能的には、WebGL2 / OpenGL ES 3.0はWebGL 1 / OpenGL ES 2に存在するすべての機能を包含します。異なるAPI関数が呼び出される方法のみが変更されました。
WebGL 1からWebGL 2に移行するには、以下の既知の下位互換性がないリストに注意してください。
WebGL 2では、多くのWebGL 1.0拡張機能がコアWebGL 2 APIに組み込まれており、これらの拡張機能は、さまざまなWebGL拡張機能のリストをクエリするときに、もはや存在するものとしてアドバタイズされません。たとえば、WebGL 1でのインスタンス化レンダリングの存在は、ANGLE_instanced_arrays 拡張機能によって提供されますが、これはWebGL 2のコア機能であるため、GL拡張機能のリストにはもはや報告されません。アプリケーションでWebGL 1とWebGL 2の両方を同時にターゲットにする場合は、機能の存在を検出するときに、拡張機能とコアコンテキストバージョンの両方の番号を確認することを忘れないでください。
上記の副作用として、機能がコアにマージされると、機能の呼び出しに使用する特定の関数名が変更されました。つまり、WebGL1 / GLES 2コンテキストでは、関数 glDrawBuffersEXT()
を呼び出しますが、WebGL2 / GLES 3.0では、サフィックスなしの関数 glDrawBuffers()
を呼び出す必要があります。
コアWebGL 2仕様に採用されたWebGL 1拡張機能の完全なリストは次のとおりです。
ANGLE_instanced_arrays EXT_blend_minmax EXT_color_buffer_half_float EXT_frag_depth EXT_sRGB EXT_shader_texture_lod OES_element_index_uint OES_standard_derivatives OES_texture_float OES_texture_half_float OES_texture_half_float_linear OES_vertex_array_object WEBGL_color_buffer_float WEBGL_depth_texture WEBGL_draw_buffers
これらの拡張機能は、機能的な変更なしに採用されたため、WebGL2 / GLES 3.0コンテキストを初期化するときに、拡張機能の存在を確認することなく直接使用できます。
注目すべき追加は、WebGL 2が新しいGLSLシェーダー言語形式を導入したことです。WebGL 1では、シェーダーは、OpenGL ES Shading Language、バージョン1.00 で、シェーダーコードで #version 100
バージョンプラグマを使用してオーサリングします。WebGL 2では、新しいシェーダー言語バージョンである OpenGL ES Shading Language、バージョン3.00 が導入されました。これは、シェーダーコードで #version 300 es
プラグマディレクティブによって識別されます。
WebGL 2 / GLES 3.0では、WebGL 1 / GLES 2 #version 100
シェーダーを引き続き使用するか、WebGL 2 / GLES 3.0 #version 300 es
シェーダーを使用するように移行できます。ただし、WebGL 2には、WebGL拡張機能 OES_standard_derivatives
および EXT_shader_texture_lod
が #version 100
シェーダーで使用できなくなったという、下位互換性を損なう互換性がないことに注意してください。これは、これらの機能が拡張機能として存在しなくなったためです。これらの拡張機能を使用する #version 100
シェーダーは、代わりに #version 300 es
形式に書き換える必要があります。Emscriptenは、これらの拡張機能のいずれかが検出された場合に、#version 100
シェーダーを #version 300 es
形式に自動的に移行するために、文字列検索置換ベースのリンカーフラグ -sWEBGL2_BACKWARDS_COMPATIBILITY_EMULATION
を提供し、下位互換性のこの破損を隠そうとします。
WebGL 2/GLES 3.0 では、拡張機能で導入されたテクスチャフォーマットについて、多くのテクスチャフォーマットの列挙子が変更されました。WebGL 1/GLES 2 の拡張機能で使われていた、いわゆるサイズ指定なしのテクスチャフォーマットは使用できなくなり、代わりに、新しいサイズ指定付きのフォーマットを internalFormat
フィールドに使用する必要があります。例えば、format=GL_DEPTH_COMPONENT, type=GL_UNSIGNED_INT, internalFormat=GL_DEPTH_COMPONENT
でテクスチャを作成する代わりに、internalFormat
フィールドにサイズを指定する必要があります。つまり、format=GL_DEPTH_COMPONENT, type=GL_UNSIGNED_INT, internalFormat=GL_DEPTH_COMPONENT24
のように指定します。
WebGL 2/GLES 3.0 のテクスチャフォーマットに関する特定の落とし穴は、ハーフフロート(float16)テクスチャタイプの列挙値が、WebGL 1/GLES 2 の拡張機能 OES_texture_half_float
が WebGL 2/GLES 3.0 コア仕様に組み込まれた際に変更されたことです。WebGL1/GLES 2 では、ハーフフロートは GL_HALF_FLOAT_OES=0x8d61
の値で示されていましたが、WebGL2/GLES 3.0 では、列挙値 GL_HALF_FLOAT=0x140b
が使用されます。これは、コア仕様への組み込み時に、一般的に使用されていた列挙値が保持される他のテクスチャタイプ拡張機能とは対照的です。
全体として、WebGL1/GLES 2 と WebGL2/GLES 3.0 コンテキストの両方を同時にターゲットにするのを容易にするために、Emscripten はリンカーフラグ -sWEBGL2_BACKWARDS_COMPATIBILITY_EMULATION
を提供しています。これは、上記の差異を自動検出による移行で隠蔽し、既存の WebGL 1 コンテンツが WebGL 2 をターゲットにすることを透過的に許可し、それによって得られる無料の高速化を可能にします。
このエミュレーションに欠けている項目を見つけたり、このガイドを改善するためのコメントがある場合は、Emscripten バグトラッカーにフィードバックを送信してください。