Web制作にGPU処理を取り入れる

HexGLのようなWebGLのコンテンツを見ていると、これから本格的にWebがゲーム機の領域を浸食するようになっていくのかと、同じWebでもWebサイトを作る領域からすると動きの面で感心させられることしきりです。

なぜWebGLのアニメーションは滑らかなのか

さておき、WebGLのコンテンツは非常に滑らかなアニメーションを実現できているわけですが、それはWebGLがCPUではなくGPUで処理されいるからとのことです。この場合のGPUで処理をするというのはソフトウェアであるブラウザがGPUを使ったハードウェア・アクセラレーションに対応していることが条件になります。つまり、CPU上でソフトウェア(ブラウザ)が処理する部分とGPU上のハードウェアで処理する部分を切り分けられる機能をもっているということになります。

ハードウェアアクセラレーションより

ハードウェア・アクセラレーションとは、ある機能を通常の(汎用)CPUの上でソフトウェアを実行する場合より高速に行なうため、CPUに対し付加的なハードウェアを使用することである。この付加的なハードウェアを総称してアクセラレータと呼ぶ。

Web制作にGPU処理を取り入れるには

そして、現在リリースされている最新主要ブラウザはすべてGPUを使ったハードウェア・アクセラレーションに対応しているので、Webサイトを作る領域においてもGPUを使った処理というのは滑らかなアニメーションを実現するために是非取り入れたいところではあります。

WebGLにはcanvas要素が使われているのですが、GPUを使ったハードウェア・アクセラレーションに対応している場合、canvas要素があるとGPUで処理する部分として自動的にGPUレイヤーなるものを生成するらしいので、この場合はcanvas要素がGPUを使わせる処理のトリガーとなってるようです。

canvas要素のように通常のWebサイトの制作においても、特定の条件を満たすことでGPUを使ったハードウェア・アクセラレーションに対応している場合はGPUレイヤーを任意の要素で生成することができるようです。

その特定の条件となっているものとして有名なのがtranslate3d(x, y, z)のように3D Transform関連のファンクションを指定することのようですが、ひとつ気になる記事を読みました。

Mobile Safariで2Dアニメーションを実行する時に、本当にtranslate3dを使用すべきなのか検証してみましたより

そもそも、なぜ滑らかなアニメーションの実現のために「translate3d」の使用が推奨されているのかと言いますと、「3D処理の関数(translate3d)を使うことで、初めてGPUアクセラーレーションが有効になる」という情報が浸透しているからです。

つまり、「translate3d」を使えば、アニメーション時のレンダリングをソフト(ブラウザ)ではなく、ハード(スマートフォンのグラフィックスチップ)が担当するようになるよー、ハードが担当するから非常に滑らかなんだよー、という事ですね。

私もこの情報を疑うことなく、当たり前のように「translate3d」を使ってアニメーションを実現してきました。

ところが先日、Android端末におけるtranslate3dが及ぼす不具合についての検証を行った際に、translateで実行したアニメーションがtranslate3dで実行したそれと大差ないように感じまして、つまり、「translateでもGPUアクセラーレーションが有効になっているのではないか?」と、疑問に思った次第です。

確かに、多くの情報が出回っているように現時点ではSafari、Chromeなどでは、3D Transform関連のファンクションがあると自動的にGPUレイヤーを生成するようですが、これはブラウザの実装側の問題であって、ブラウザ側のGPUを使ったハードウェア・アクセラレーションへの対応の進化次第では他のCSSプロパティでもGPUレイヤーを生成するようになる(もしくは既になっている)かもしれませんし、逆に現在トリガーとして機能していたものが将来的にはトリガーにならなくなる可能性もあるかもしれません(たとえば過去にiOSで-webkit-transform-style: preserve-3d ;がトリガーになっていたがiOS6からは無効になっている→参考:iOS6 html hardware acceleration changes and how to fix them)。

そこでGPUで処理しているのかどうかを視覚化できないものかと情報を探していたところVisualizing WebKit's hardware accelerationという記事を見つけました。

さて、この記事によると環境変数を与えたSafariを起動させることで確認できるようで、

CA_COLOR_OPAQUE=1 /Applications/Safari.app/Contents/MacOS/Safari

上記のコマンドをターミナルで入力します(注:Mac OS X 10.9のSafari7.0ではエラーで起動しませんでした。Safari6.1も含めて、6系は起動します)。終了したい時はcontrol+Z

これで早速いくつかサイトを見てみると、たしかに、処理負荷の高そうな箇所に応じて緑、より高いところは赤という感じで色分けをしてくれます。先の記事の説明によると赤くなっているところがハードウェア・アクセラレーションを使っているところという説明があります。

  • Apple
    アップルのスクリーンショット
  • Facebook
    Facebookのスクリーンショット

このスクリーンショットを見ると、Appleのサイトでは、アニメーションしている部分が赤くなっていることが分かり、Facebookのようにアニメーションが使われていないサイトは真緑になりました。

ところが、肝心のGPUを使ったハードウェア・アクセラレーションになるトリガーとされている3D Transform関連のファンクションのサイトを見ると、どうもこの着色が逆にまったく何も反応しませんでした。そこで、最初に紹介した記事に「1. transform系を使わずに、left/topを使ったパターン, 2. traslateを使ったパターン, 3. traslate3dを使ったパターン」を比較したちょうど良いサンプルがあったので、そのスクリーンショットを見てみましょう。

1. transform系を使わずに、left/topを使ったパターン, 2. traslateを使ったパターン, 3. traslate3dを使ったパターンの比較
アニメーションを比較したサンプルのスクリーンショット

このようにTransform関連のファンクションを使ったアニメーションでは、着色対応として全く反応しません。また、可視化しているSafariの環境変数について調べようにも、あまり詳しい情報が見つからなかったので、ここからは憶測が入るのですが、ひとつ仮説をたてます。

視覚化しているのはCPU処理で負荷の高い箇所?

この可視化してくれているのはGPUでの処理を示しているのではなく、CPUでの処理負荷の高い箇所に応じて反応しているのではないかと考えました。その理由としてはVisualizing WebKit's hardware accelerationの記事は既に2年以上前の記事なので、当時はまだCSSアニメーションがGPUで処理している条件になっていなかったのではないかということです。現在、最新のスマートフォンで滑らかにアニメーションが処理されているTransform関連のファンクションをつかった部分は、このデスクトップの可視化Safariではすべて全く無着色状態だからです。

つまり、逆にアニメーション部分なのにこの可視化状態で着色されていない部分がGPUを使ったハードウェア・アクセラレーションで処理されている部分になっていると推測します(このあたりはSafariの環境変数を深堀できる資料があれば良いのですが...)。

結論

可視化の結果や、実際のスマートフォンでの反応を見る限りでは、現時点でGPUを使ったハードウェア・アクセラレーションで処理させる部分には、3D Transform、2D Transformに関わらずTransformのファンクションを使ったアニメーションを採用することでできると言えそうです。つまり、トリガーとなるのは3D Transform関連のファンクションだけでなく、2D Transform関連のファンクションを指定してもGPUレイヤーが生成され、そのアニメーションはGPUで処理されていると考えられます。

ひとつ要注意点として、タッチデバイスでのフリック操作によるタッチの始点から終点までアニメーションを持続的に発生させるようなものについては、3D Transform関連のファンクションを用いた方が圧倒的に滑らかに動く結果になりました。そのためフリック操作についてはtranslate3d(x, y, z)などの3D Transform関連のファンクションを用いられることをおすすめします。ただし、3D Transform関連のファンクションをフリック操作に用いた場合、touchstarttouchmove中にevent.preventDefault();を伴ってスクロール挙動を制止しなければZ軸の仕様でiOSでは上下に画面全体がガタガタすることがあります(どのみちAndroidでtouchendを正常に発火させるのにevent.preventDefault();を指定しておく必要があるのでセットで必要になると考えましょう)。

Web制作にGPU処理を取り入れるための手順

さて、前置きが長くなりましたが、現時点ではSafari、Chrome、Blink版Operaにおいては条件が同じと思われるので、Transform関連のファンクションを指定したアニメーション部分はGPUで処理できるようになっていると考えられます。そこで、次に実際にTransform関連のファンクションを指定したアニメーションを取り入れる手順を紹介します。

例えば、jQueryで次のようなアニメーションを書いていたとします。このX軸へ300ミリ秒かけて100px動かしているjQueryのアニメーションはCPUで処理され、再描画を続けながら動いているように見せるだけなので、アニメーションとしての動きは滑らかではありません。

$('.target').stop().animate({marginLeft:'100px'},{duration:300});

これをTransform関連のファンクションとCSSアニメーションをサポートしている環境に対しGPUで処理できる場合にはGPUで処理できるように代替処理内容に書き換えます。今回の例では、CSSアニメーションのタイミング操作にtransitionプロパティを用いるものとします(animationプロパティを用いることもできます)。そのためTransform関連のファンクションとtransitionに対応していない古いUAに対しては従来通りjQueryでのアニメーションを使うものとします。

if(window.applicationCache){
	$('.target').css({
		'-webkit-transition':'-webkit-transform 300ms ease',
		'-webkit-transform':'translate(100px, 0)',/*chrom12+, Safari4+, Opera15+*/
		'-o-transition':'-o-transform 300ms ease',
		'-o-transform':'translate(100px, 0)'/*Opera10.5+*/
		'transition':'transform 300ms ease',
		'transform':'translate(100px, 0)'/*IE10+, FF16+*/
	});
}else{
	$('.target').stop().animate({marginLeft:'100px'},{duration:300});
}

window.applicationCacheはオフラインWebアプリケーションのオブジェクトでIE10からサポートされており、ここではtransitionをサポートしているかを判断する条件分岐に使っています。JavaScriptでのアニメーションしか再現できない古いUAと新しいCSSのアニメーションを享受できるUAとの境界を引くのにwindow.applicationCacheを実装しているかどうかはちょうど良い条件分岐に使えます(参考:Can I use offline web applications? / Can I use CSS3 Transitions?)。

なおタッチデバイスにおけるフリックなどのタッチ操作に応じたアニメーションの場合はtranslate3dを用いた方が滑らかに動くので、その場合の条件判断にはwindow.matchMediaを用いると良いでしょう(参考:Can I use matchMedia?)。旧Presto版Operaについては、translate3dをサポートしていないので、2Dのtranslateを使います。

if(window.matchMedia){
	$('.target').css({
		'-webkit-transition':'-webkit-transform 300ms ease',
		'-webkit-transform':'translate3d(100px, 0, 0)',/*chrom12+, Safari4+, Opera15+*/
		'-o-transition':'-o-transform 300ms ease',
		'-o-transform':'translate(100px, 0)'/*Opera10.5+*/
		'transition':'transform 300ms ease',
		'transform':'translate3d(100px, 0, 0)'/*IE10+, FF16+*/
	});
}else{
	$('.target').stop().animate({marginLeft:'100px'},{duration:300});
}

また古いUAの対応を考慮しなくてもいいCSSアニメーションだけの対応で済ませられる場合は、CSSファイルに記述すれば良いでしょう。次の例では、Y軸へ300ミリ秒かけて-100px動かしています。

.target{
	-webkit-transition : -webkit-transform 300ms ease ;
	-webkit-transform : translate(0, -100px);/*chrom12+, Safari4+, Opera15+*/
	-o-transition : -o-transform 300ms ease;
	-o-transform : translate(0, -100px);/*Opera10.5+*/
	transition : transform 300ms ease;
	transform : translate(0, -100px);/*IE10+, FF16+*/
}

jQueryやTransformを使っていないCSSアニメーションは再描画を続けながら動いているように見せているだけですが、GPUでの処理では、translateの場合、GPUレイヤーが指定されたX軸やY軸へ移動することになるので、非常に滑らかに動くようになることが期待できます。デスクトップではこの程度のアニメーションでは大した差はでませんが、とくにスマートフォンではその差は顕著にわかるので、GPU処理を取り入れるか取り入れないかは特にモバイル分野のWeb制作においては生命線になると言えます。

まとめ

jQueryのアニメーションやTransformを使っていないCSSアニメーションは再描画を続けながら動いてくだけのCPUで処理されるアニメーションですが、GPUを使ったハードウェア・アクセラレーション処理を享受できる環境には、現時点ではGPUレイヤーが生成されるTransform関連のファクンションを使ったアニメーションを採用することで可能になると考えられます。

GPUでの処理であれば、アニメーションは非常に滑らかになることが期待できるので、Web制作においても必要に応じてTransformを使ったCSSアニメーションを取り入れるように心がけたいものです。ちなみに、Transform関連のファンクションには次のようなものがあるので、現在使用しているアニメーションから代替できそうなものがないかを考えてみると良いでしょう(参考:The Transform Functions)。

  • 座標移動(translate, translate3d, translateX, translateY, translateZ)
  • 拡大縮小(scale, scale3d, scaleX, scaleY, scaleZ)
  • 回転(rotate, rotate3d, rotateX, rotateY, rotateZ)
  • スキュー(skew, skewX, skewY)
  • マトリクス処理(matrix, matrix3d)
  • 透視投影(perspective)

なお、当然ですが、GPUのリソースであるメモリにも限りがあるので、すべてをTransformを使ったCSSアニメーションにすれば良いというわけでなく、動きが鈍い部分を発見したら、CPUでの処理においても再描画の回数や再描画される範囲を狭められないか等を検討した上で、必要に応じてGPUで処理できる形を取り入れるというのが最良の手段ではないでしょうか。

Updated / Published