USD・Hydra の環境変数と仲良くなる

https://qiita.com/advent-calendar/2021/usd

今回は怪文書ではなく普通の記事です。

# 環境変数はともだち
USD・Hydra は USD 本体のビルド時の設定よりも、環境変数によって動的に挙動が変化したり、トラブルシューティングのために多くの情報を出力したりすることができます。実際プロダクションにおいては USD のデバッグ版が必要になることは稀で、多くの場合環境変数によって設定の不具合やファイルの不足、パスの不備などを見つけることができます。

この記事では、USD 本体の挙動を制御する環境変数、デバッグのための情報出力する環境変数、Hydra の制御用環境変数の３つを解説します。Windows / Linux どちらでも同じように適用可能ですが、「環境変数はプロセスについてまわる」という点には注意してください。たとえば Windows でユーザ環境変数を変更したら、その後に起動したプログラムから適用されます。コマンドプロンプト内で設定した環境変数は、そのコマンドプロンプトから起動した別のプログラムに引き継がれます。
プロダクションにおいて複数人で DCC ツールを使う場合、同じ環境に揃える・再現性のある環境を作る、というのは非常に基本的かつ重要な手続きになってきます。ここをうまくコントロールできないと、他人のシーンファイルをもらってきてレンダリングしたら結果が変わるんだけど・・・なんていうのは日常茶飯事でしょう。

デバッグ環境変数は、usdview の Window -> Debug Flags メニューで一覧したり操作したりすることができます。usdview でセットしてもその usdview 内でしか有効でないことには注意してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64544/6b0c3585-ed6c-5945-a4ca-c993f81377dc.png)

以下 windows での指定方法で説明しますが、linux でも export / setenv などにより同様に設定可能です。

# TF_DEBUG
デバッグ用の情報出力などを制御するのは TF_DEBUG 環境変数です。これに特定の文字列を空白区切りでセットすると、個別のデバッグ機能が有効になります。ごく一部ですが有用なものを紹介します。

### TF_DEBUG=SDF_LAYER

```
F:\Kitchen_set\assets\Ball>set TF_DEBUG=SDF_LAYER
F:\Kitchen_set\assets\Ball>usdview Ball.usd
SdfLayer::FindOrOpen('Ball.usd', '< >')
Sdf_LayerRegistry::FindByIdentifier('f:/Kitchen_set/assets/Ball/Ball.usd') => Not Found
Sdf_LayerRegistry::FindByRealPath('f:\Kitchen_set\assets\Ball\Ball.usd') => Not Found
Sdf_LayerRegistry::Find('Ball.usd') => None
SdfLayer::_OpenLayerAndUnlockRegistry('Ball.usd', 'Ball.usd', 'usd', '< >', metadataOnly=False)
SdfLayer::SdfLayer('Ball.usd', 'f:/Kitchen_set/assets/Ball/Ball.usd')
Sdf_LayerRegistry::InsertOrUpdate(SdfLayer('Ball.usd', 'f:\Kitchen_set\assets\Ball\Ball.usd'))
Sdf_LayerRegistry::FindByIdentifier('Ball.usd') => Found
SdfLayer::_Read('Ball.usd', 'f:/Kitchen_set/assets/Ball/Ball.usd', metadataOnly=false)
SdfLayer::_Read - fetched 'Ball.usd' to local path 'f:/Kitchen_set/assets/Ball/Ball.usd'
```

レイヤー（USD ファイル）は USD 実行プロセス中ではレイヤーレジストリという弱参照辞書で管理されますが、このなかでどのようにキャッシュされるかがわかります。これを順番に見ていくだけでも USD の動作の理解が深まりますので、最初の部分だけ少し詳しく見てみましょう。

まず FindByIdentifier() が Not Found を返しています。USD のレイヤーはメモリ上では Identifier（識別子）で管理されていて、通常はファイルのパスが識別子に使われます。ファイルパスではないときはどういうときか？というと、たとえば無名レイヤーを作った場合です。python から

```
layer = Sdf.Layer.CreateAnonymous()
```

をすると、

```
Sdf_LayerRegistry::InsertOrUpdate(SdfLayer('anon:000001D3A6D14AE0', ''))
```
このような表示がでますが、anon:000001D3A6D14AE0 の部分が自動生成された無名レイヤの識別子になります。

次に FindByRealPath, Find でもフルパス・ファイル名にてレジストリの検索が行われます。そしてまだこのレイヤーは読み込んだことがないとわかって、ようやくファイルをオープンし、レジストリに登録しています（SdfLayer('Ball.usd'))


### TF_DEBUG=SDF_ASSET

SDF のアセット解決の際の assetInfo のログを得ることができます。AR にも似たような機能のフラグはあります。

```
F:\Kitchen_set\assets\Ball>set TF_DEBUG=SDF_ASSET
F:\Kitchen_set\assets\Ball>usdview Ball.usd
Sdf_ComputeAssetInfoFromIdentifier('Ball.usd', 'f:/Kitchen_set/assets/Ball/Ball.usd', '')
Sdf_ComputeAssetInfoFromIdentifier:
  assetInfo->identifier = 'Ball.usd'
  assetInfo->resolvedPath = 'f:\Kitchen_set\assets\Ball\Ball.usd'
  assetInfo->repoPath = ''
  assetInfo->assetName = ''
  assetInfo->version = ''
Sdf_ComputeAssetInfoFromIdentifier('anon:0000022356148B00', '', '')
Sdf_ComputeAssetInfoFromIdentifier:
  assetInfo->identifier = 'anon:0000022356148B00'
  assetInfo->resolvedPath = ''
  assetInfo->repoPath = ''
  assetInfo->assetName = ''
  assetInfo->version = ''
Sdf_ComputeAssetInfoFromIdentifier('f:/Kitchen_set/assets/Ball/Ball_payload.usd', 'f:/Kitchen_set/assets/Ball/Ball_payload.usd', '')
Sdf_ComputeAssetInfoFromIdentifier:
  assetInfo->identifier = 'f:/Kitchen_set/assets/Ball/Ball_payload.usd'
  assetInfo->resolvedPath = 'f:\Kitchen_set\assets\Ball\Ball_payload.usd'
  assetInfo->repoPath = ''
  assetInfo->assetName = ''
  assetInfo->version = ''
```

コンポジションにより様々な usd ファイルが解決されていく様子がわかるかと思います。

### TF_DEBUG=PLUG_REGISTRATION

USD を自前でビルドしたとき、またプロダクションで使うときよくハマるのがプラグインのロードです。この環境変数をつけておくと USD プラグインの何をロードしたかよくわかります。USD は標準では PXR_PLUGINPATH_NAME にプラグインのパス名を指定する、というちょっと変な仕様になっていますが、このパスの中に plugInfo.json が適切な書式で記述されている必要があります。またプラグインが別の DLL に依存している場合に、その DLL にパスが通っていないとプラグインが読めないケースがありますが、同様に PLUG_LOAD, PLUG_LOAD_IN_SECONDARY_THREAD, PLUG_INFO_SEARCH などの環境変数などと合わせて情報を集めてトラブルシューティングすることができます。

PLUG_ 系のデバッグ環境変数を全て有効にするのは

```
set TF_DEBUG=PLUG_*
```

とできます。

# TF_DEBUG 以外の動作制御環境変数

|環境変数|デフォルト|動作|
|----|----|----|
|PXR_WORK_THREAD_LIMIT|0|マルチスレッドの並列数を制限する|
|PXR_AR_DISABLE_PLUGIN_RESOLVER|0|AssetResolver を無効化する|
|USD_DEFAULT_FILE_FORMAT|usdc|.usd ファイル書き込み時のデフォルトフォーマット|


また hydra や Glf （描画用ライブラリ）にも直接コントロールする環境変数があります。

|環境変数|デフォルト|動作|
|----|----|----|
|HD_ENABLED|1|Hydra 有効にする（usdImaging の変数。旧 GL viewer が起動）|
|HD_ENABLE_GPU_FRUSTUM_CULLING|1|GPU フラスタムカリングを有効|
|HD_ENABLE_GPU_INSTANCE_FRUSTUM_CULLING|1|GPU インスタンスフラスタムカリングを有効|
|HD_ENABLE_OPENSUBDIV3_ADAPTIVE|1|サブディビジョンサーフェスのテセレーションを有効|
|HD_MAX_VBO_SIZE|1*1024*1024*1024|VBO バッファアグリゲーションの最大値を設定する|
|HD_ENABLE_REFINED_CURVES|0|カーブを常にテセレーションする|
|HD_ENABLE_DOUBLE_MATRIX|0|座標変換の行列を倍精度で行う|
|HDST_ENABLE_RESOURCE_INSTANCING|1|リソースインスタンスを有効|
|HGIGL_DEBUG|0|HgiOpenGL デバッグを有効化|
|HGIMETAL_DEBUG|0|HglMetal デバッグを有効化|
|GLF_ENABLE_SHADER_STORAGE_BUFFER|1|SSBO を利用|
|GLF_ENABLE_BINDLESS_TEXTURE|0|バインドレステクスチャを利用|
|GLF_ENABLE_BINDLESS_BUFFER|0|バインドレスバッファを利用|
|GLF_ENABLE_MULTI_DRAW_INDIRECT|1|MDI 描画を利用|
|GLF_ENABLE_DIRECT_STATE_ACCESS|1|DSA を利用|
|GLF_ENABLE_SHADER_DRAW_PARAMETERS|1|シェーダ描画パラメータを利用|
|GLF_DRAW_TARGETS_NUM_SAMPLES|4|オフスクリーンバッファの MSAA|


# デバッガ支援

TF_DEBUG に以下の変数をセットしておくと、ランタイムで Warning/Error が出たときにデバッガアタッチ用のダイアログを出してくれます。C++ プラグイン開発時などに非常に便利です。

TF_ATTACH_DEBUGGER_ON_ERROR
TF_ATTACH_DEBUGGER_ON_FATAL_ERROR
TF_ATTACH_DEBUGGER_ON_WARNING

実行時に USD がエラーや警告を出すタイミングで、これを出してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64544/86550152-c875-920c-fe49-0e417abffcd3.png)

# おわりに
USD のコンポジションをデバッグするのはそれなりにコツのいる作業ですが、usdview と環境変数を組み合わせるとわざわざデバッグ版を用意しなくても相当のことはわかります。ただでさえパイプラインは複雑になりがちなのに、ライブラリまで複雑なのでハマると大変ですが、いろんな角度から情報を集めて、なんとか乗り越えていきましょう