TouchDesigner

TouchDesigner のパフォーマンスチューニング & TIPS

TouchDesignerのパフォーマンスチューニングの時に気をつけることと、今まで触ってみて気付いたTIPSをつらつら書いていこうとおもいます。

サンプルファイルは こちら

インストールしたら最初にやる事

Adaptive Homing by Defaultをオフにする

メニューバーの、 Edit > Preference から、Geometry タブの中にある Adaptive Homing by Default をオフにします。

homing_setting.png

これが入っているとSOPのプレビュー領域が常にジオメトリがカメラの範囲内に入るように計算してくれるようになりますが、裏を返せば毎フレーム全ポイントをなめていってジオメトリの中心と大きさを算出する、というものなので、ポイント数が増えてくるとプレビューだけで相当な重さになります。

そんなにジオメトリを眺めていい事もないと思うので絶対にデフォルトで切るようにしましよう。

どうしても使いたい場合はSOPを選択してViewer Avtiveにした後に右クリックメニューから有効化することができます

homing.png

バージョン管理は他でやる事が多いのでバックアップは作らない

好みの問題になりますが、個人的にはバックアップファイルでプロジェクトのフォルダが荒れるのが嫌だな~… とおもうので思い切ってバックアップは作らず、git、svnなどでバージョン管理をしています。

Preference > General > Incremental Filename on SaveOff で切ります。

CPU Cook Timeをこまめに気にする

オペレーターを中クリックするとCPU Cook Time 又は Children CPU Cook Timeが出るので、ちょいちょい気にしながら確認するようにしましょう。

cpu_cook_time.png

指標としては1フレームが16msで終わればいいので、単純計算で1msかかっている処理であれば16個まで増やせる、ということにまります。

無闇矢鱈と最適化に走っても逆に生産性が落ちてしまうので、処理時間とシーン内での処理の重要性を意識することが大事です

逆に言えば処理が重くても、ものすごいキーになるような計算なのであれば、プロジェクトのフレームレートを30fpsとか24fpsに設定して成立すればOKな訳です。大切なのは60fpsをキープする事ではなくて、いい感じの絵を出す事です!

プロジェクト全体のパフォーマンスを確認するには Performance Monitor Dialog も参考になります

いらないGeometry系のプレビューを切る

色々な所で内容が表示されると便利なのですが重くなる原因にもなります。

必要ない所では、Preview、Display、Render、Templateのフラグを切っておきましょう。

render_flags.png

個人的にはTOP、ある程度のサンプル数までのCHOPは表示しててもそこまで重くならないと思っているので放置しています

解析系のTOPは解像度が低くても言うほど精度に差がないので落とす

Trace SOPなど、一部の画像解析系のオペレーターは解像度を上げているとその分処理が重くなる時があります。

処理が重いなと感じたら入力の解像度を落として出力にどの程度の影響が出るかを見極めるようにしましょう。(大抵のOpenCV系の処理は解像度落としてもそんなに影響ないです)

クオリティの低下が許容内であれば軽い方がいいですからね…

Blur TOPのPre-Shrinkは見た目に問題がないレベルまで下げる

TouchDesignerだとブラーも簡単にかけれるので多様してしまいがちですが、GPU負荷の高い計算である事に変りはありません。

Filter Size の数値を上げるとボケの半径が大きくなりますが、その分処理負荷が高くなります。その時にPre-Shrink の数値を上げると1/N倍にダウンサンプリング → ブラー処理 → N倍にアップサンプリングというような処理をしてくれるので、結果的にブラー処理のピクセル数が削減でき、パフォーマンスが上がります。

絵的には Filter Size でボカした方が綺麗なのですが、そこまで綺麗にぼかす必要がないのであれば可能な限り Pre-Shrink のほうでボカすようにしましょう

ちなみにExprementalのほうだとオペレーターを中クリックで GPU Cook Time も表示されるようになっているので、GPU側の最適化もやりやすくなっています :thumbsup:

数が必要な場合、Copy SOPではなくてGeomerry Instanceを使う

/project1/COPY_SOP_vs_GEOMETRY_INSTANCEを参照

個人の感想ですが、TouchDesignerだとオブジェクトの数を出そうとするとSOPで対応するのは結構キビシイなと感じでいます…

100個の球体を複製という処理をCopy SOPとジオメトリインスタンスを使った方法と比較するとパフォーマンスに60倍の開きがありました

表現の幅もジオメトリインスタンスを使ったほうが広い事が多いので、Copy SOPの出番は見付ける方が難しいのではないかと思います…

Script CHOPとConstant CHOPの使いわけ

/project1/SCRIPT_CHOP_vs_CONSTANT_CHOP を参照

CHOPの値に簡単なエクスプレッションを書き込みたい、とか、他のCHOPを参照した値を取り出したいといった時にScript CHOPConstant CHOPのどちらを使ったらいいか?というものです。

個人的には扱うCHOPのデータが1サンプルの時にはどちらでもいいのかなと思いました。軽く検証してみましたが、1サンプル程度のデータであればパフォーマンスはどちらも変わりありませんでした。

ただ、マルチサンプルのデータを扱うとなるとConstant CHOPでは処理できないのでScript CHOPになると思います。

似たようなものとしてExpression CHOPがありますが、僕は全然使ってないです。

Pythonのあれこれ

op(NAME) や op(NAME)[CHANNAME] の文字列ルックアップは地味に重いので可能であればキャッシュする

/project1/CACHE_CHANNEL_OBJECT を参照

CHOPのオブジェクトはPythonから、チャンネルインデックス、又はチャンネル名で参照できます

my_chop = op('my_chop')
my_channel = my_chop[0] # index access
my_channel = my_chop['chan1'] # string access

ですが、簡単に計測した所、チャンネル名とインデックスのアクセスを比較するとチャンネル名のほうが6倍ぐらい遅いです

なので、必要であればルックアップしたオブジェクトをキャッシュして使います。

chans = [] # グローバルレベルに適当な配列を用意する

def onCook(scriptOp):
    input = scriptOp.inputs[0]

    if not chans: # 配列が空の場合キャッシュする
        for i in range(input.numChans):
            chans.append(input['chan%i' % i]) # ここでは名前アクセスでもOK

    scriptOp.clear()

    scriptOp.numSamples = input.numChans
    scriptOp.appendChan('chan1')

    for i in range(scriptOp.numSamples):
        scriptOp[0][i] = chans[i][0] # キャッシュした配列にアクセスする

キャッシュした副作用として、入力のチャンネル数が変わると参照が外れてバグの原因になるので、キャッシュをクリアする仕組みを作るか、チャンネル数が変わった場合カット/ペーストで強制的にオブジェクトを作り直すか、などの対応が必要になるかと思います。注意

cProfileでスクリプトの実行をプロファイルする

/project1/USE_CPROFILE を参照

Python側でやる事の規模が大きくなってくると、TDのプロファイラではPython側の処理が補足できないので、どこで時間がかかるかを見極めるのが難しくなります。

そういう時はPythonのcProfileモジュールを使ってプロファイルしましょう。

適当なText DAT を作って以下のようなコードを右クリックからRun Script、または選択してCtrl+Rしてみましょう

import cProfile
pr = cProfile.Profile()

pr.enable()

for i in range(1000):
    mod('execute1').onFrameStart(None) # ここで計測したい関数を実行

pr.disable()

import pstats
stats = pstats.Stats(pr)
stats.sort_stats('tottime')

stats.print_stats()

上記の例だと1000回関数を実行した時にどれぐらいの時間がかかっているか、などの情報が表示されると思います。

さらにその関数の中でも、どの部分にどれぐらいの時間がかかっているか等も表示されるのでやみくもに最適化をするよりもかなり時間が節約できます :ok_hand:

何でもないSOPが異常に重い

/project1/WAITING_DISPLAY_SYNC を参照

たまに、CPU Cook Timeを見ると何もしてないSOPが10-14msくらいかかっている時があります。

これはレンダリングがディスプレイの同期待ちでブロックしている時間だと思われるので特に気にしなくても大丈夫だと思います

ジオメトリをまとめる

merge_geometry.gif

ネットワークが同一改装で横にひろがると、表示すべきオペレーターが多くなってしまうのでパフォーマンス的にも構造的にもあまりよくありません。なので、適当な意味的なまとまりでグループを作るのはいいアイデアです

↑ のgifは右クリックからCollapase Selectedで複数のオペレーターをBase COMPに入れた後にChange COMP Typeする事で、複数のジオメトリをまとめて単一のジオメトリにする操作です。

ジオメトリをまとめると、親のほうでトランスフォームやマテリアルを変更すると子のほうも一気に適応されたりなどとても使い勝手が高いので、覚えておいて損はないテクニックだと思います

イベントと操作を分離する

/project1/SEPARATE_EVENT_AND_OPERATION を参照

Execute系のDATは結構よく使いますが、たとえばExecute DATonStartのイベントで何かしたいと思った時にテストするのにTouchDesignerの再起動が必要になったりしてとてもめんどくさい時があります。

そういう時はイベント系のDATからは、別のText DATのスクリプトをキックするようにしましょう。

op('MY_TEXT_DAT').run()

のような感じです。.run() には引数も渡せます 詳しくはこちら

Text DAT は他のExecute系のDATに比べて簡単に編集できて、Ctrl+Rで簡単に実行できるのでテストの効率がよく、開発時間の短縮とバグ率の低下が期待できると思うのでオススメです

OSCを受ける時のTIPS

OSC In CHOPの注意点

/project1/DROP_OSC_FRAME を参照

TouchDesignerではOSCを受信するのにCHOPとDATの両方がありますが、1フレームのみトリガーなどのメッセージを受ける時にはOSC In CHOPはオススメできません

理由は、処理が重くなったりしてTD全体のFPSが下がってしまった場合にCHOPのCookの処理が走らなくなる時があるという物なのですが ↓ のgifを見てもらったほうが早いと思います

osc_drop_frame.gif

Hog CHOP で仮想的にCPUリソースを食いつぶした時にどういう挙動になるかという奴なのですが、デフォルトのままのOSC In CHOPの設定だとPulseをとりこぼしているのがわかると思います。

Queued の設定を入れるとちゃんと動いているように見えますが、若干レイテンシが発生するようです (ここのあたりあまりわかっていません… レイテンシが解消されればそっちのほうが楽なので、知ってる方教えてください!)

なのでOSCの入力を使うのであれば OSC In DAT のほうを使うのをオススメします。こちらの場合はCookの間に入ってきたメッセージが配列として取得できるので、ユーザー側で任意を処理を入れることができます

ただ、上記の問題は1フレームのみの取りこぼすと大変な事になる系のトリガーの話なので、センサーからの連続量をとりたい等の用途であれば普通にOSC In CHOPで必要十分です。

そのへんはケースバイケースですが、念のため Hog CHOP を走らせてみてFPSが落ちてもちゃんと動作するかの確認はしておいた方がいいと思われます

人生を左右する1パケットもありますので、取りこぼさないようにしたいですね!

OSC In DAT のメッセージをルーティングする

/project1/ROUTE_OSC_DAT を参照

OSC In DAT を普通に使っていると長々としたif - elif大会になってしまうので、外部のTable DATを参照してルーティングのテーブルを作ってしまおうというアイデアです

route_osc.png

さらに前述した外部Text DAT呼び出しのパターンを組み合わせるとさらに見通しがよくなりますね

なるべく参照ではなく接続を使う

これは完全に好みの問題になってしまいますが、個人的にはSelect系のオペレーターを使った参照や、エクスプレッションでop(NAME)を使った参照を多様すると、だんだんネットワークが読みづらくなり、これ消してもいいよなと思って考え無しに消すと実は色々な所で参照されていて盛大にバグる、みたいな事が起きやすいなと思っています。

なので、参照を使う場合はできるだけ同一階層内に留めておいて、階層をまたぐ場合はそれぞれInOutを使って接続する、全体から参照したいグローバルなオペレーター(通信系、デバイス系) は、/GLOBAL みたいな階層を作ってそこに絶対に配置するようにする、といった工夫をするだけでシステムの堅牢度が上がるのではないかと思って最近はネットワークを組んでいます。

まとめ

TouchDesignerは非常によくできた環境で、素早くクオリティの高いアウトプットができますが、ある程度ソフトウェア設計的な考え方を取り入れてネットワークを組んでいくとさらに生産性が上がるのではないかと思います。

ビジュアルプログラミング環境でのソフトウェア開発哲学のようなものは ここ に非常によくまとまっているので一読をオススメします :ok_hand:

長々とあまりまとまりのない記事になってしまいましたが、このあたりで!