カテゴリー : iOS

UIViewとCALayerとOpenGLテクスチャと


ずーっと長年疑問に思っていた事が、ようやく今日理解のとっかかりを得た。

話の始まりは、UIViewの内容を画像として取り出したい、というところから。普通に説明されるのは、UIImageGraphicsContextを作って、そこにCALayer取得してdrawInContext:しろ、ってことだよね。ざっと書くなら、

    UIGraphicsBeginImageContext(view.frame.size);
    [view.layer drawInContext:UIGraphicsGetCurrentContext()];
    image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

こんな感じ。確かにこれで取得できるけど、なんか嫌じゃない?どこがっていうと、drawInContext:を呼び出して、わざわざもう一回描画させているところ。もうすでに画面に描かれているんだから、それを直接取得する事はできないのか?

iOSでは、UIViewは常に背後にCALayerがいて、表示する画像とかエフェクトとかレンダーツリーとかはそっちが管理している。Layer-backed Viewってやつね。CALayerはGPUを使ってレンダリングするので、何かしらOpenGLのテクスチャと関連するものを持っているはず。

予測はしていたんだけど、それがどこにどんな形であるのかが、ずーっと疑問だった。何度も調べては挫折していた。

それが、こりずにまた調べていたんだけど、ようやくそれっぽいのを見つけた。drawRect:を実装したUIViewを画面に貼付けて、画面に描画された後で、そのビューのCALayerのcontentsを調べる。そうすると、自分では何も設定していないのに、なんか入っているじゃん!

ログ吐かせてみると、CABackingStoreというオブジェクトだった。こいつか!

<CABackingStore 0x1ed909d0 (buffer [640 960] BRGX8888) (buffer [640 960] BRGX8888)>

気づいてみたら、簡単な話だった。しかし簡単な話だから、いままであえて実験する気が起きなかった。プログラミングの調査では、確認していないものはすべて疑え、という教訓だ。

名前さえ分かってしまえば、こっちのものだ。CABackingStoreについて調べると、どうやらこれはCの構造体らしい。Core Foundationのオブジェクトタイプね。いくつかの関数が用意されている中に、CABackingStoreGetCGImage()という魅力的なものを発見。たぶん、こうやって使うんでしょう。

    CGImageRef cgImage;
    cgImage = CABackingStoreGetCGImage(view.layer.contents);

こうやると、確かに画像が取れた!おぉ、いいんじゃね、これ。

さらにCABackingStoreには、 CABackingStoreRetainFrontTexture()って関数もあるらしい。これを呼ぶときっと、このCALayerの描画に使われるOpenGLテクスチャが取得できるんでしょう。試しに呼んでみると、なんか返してくる。ただし、Objective-CやCore Foundationのオブジェクトではないっぽい。

これはなんだろう、とあちこち探しまわると、CA::Render::TextureというC++オブジェクトを発見。これかな?きっとここから、OpenGLテクスチャが取得できるんでしょう。

ということで、UIViewとCALayerとOpenGLの関係性が、コードレベルで分かってきたような気がする。あ、いちおう念のために書いておくけど、これらはとうぜんプライベートAPIなので、良い子は使わないようにお願いします。