お世話になっております。
以下の環境で StaticMesh アセットの Cook determinism を調査しています。
検証環境:
- UE version: 5.7.4-based custom branch
- TargetPlatform: Windows
同一の checkout / content revision / Cook 設定から Pass1 / Pass2 形式で Cook し、Pass2 では `-diffonly` により Pass1 の cooked package と Pass2 の in-memory cook result を比較しました。
その結果、ソースアセットに変更がないにもかかわらず、多数の StaticMesh cooked package でバイナリ差分が発生しました。ログ全文や具体的なアセットパスは社内情報を含むため省略します。
調査の結果、StaticMesh の optional index buffer availability と optional index buffer payload の解釈が、load / save / cook 経路で一致していないことが原因ではないかと考えています。
現在確認している挙動では、AvailabilityInfo の packed bits 自体が単独で非決定的に変動しているというより、load 時に CVar や初期 `bHas*` state から計算される `bEnableDepthOnlyIndexBuffer` / `bEnableReversedIndexBuffer` が、既に serialized されている optional index buffer payload を dummy / discard / `ClearMetaData()` 側へ流す gate として作用している点が問題の中心ではないかと考えています。
その結果、後続の save / cook 経路で availability state と実際の optional buffer payload の整合が崩れ、DiffOnly 差分として表面化しているように見えます。
関係している情報源は、Serialized AvailabilityInfo の packed bits、ロード済み optional index buffer data の有無、`bHasDepthOnlyIndices` / `bHasReversedIndices` / `bHasReversedDepthOnlyIndices`、`r.SupportDepthOnlyIndexBuffers` / `r.SupportReversedIndexBuffers`、および depth-only / reversed index buffer に対する serialize / discard / `ClearMetaData()` の gate です。
Diff stack 上は、主に `FRawStaticIndexBuffer::Serialize()`、`FStaticMeshLODResources::SerializeBuffers()`、`FStaticMeshLODResources::Serialize()`、`FStaticMeshRenderData::Serialize()`、`UStaticMesh::Serialize()` 経由の差分として確認しました。対象 payload は主に `ReversedIndexBuffer`、`DepthOnlyIndexBuffer`、`ReversedDepthOnlyIndexBuffer` です。
なお、Pass2 から `-iterate` を外した control run でも StaticMesh 差分は再現しました。そのため、`-iterate` 自体が主因ではなく、`-diffonly` 単体でも入る previous cooked package on disk と current in-memory cook buffer の比較経路で検出されているものと考えています。
DDC が有効に効いている状態では再生成や serialize 経路が回避される可能性があるため、検証時には DDC 再利用を避ける設定にしたうえで、Cook 前に `ResavePackages` を実行し、StaticMesh の load / save / serialize 経路を明示的に通しました。
この挙動を安定化するため、ローカルで Engine patch を適用しました。修正の主旨は以下です。
1. `SerializeBuffers()` の load 時には、既に serialized されている optional index buffer payload を CVar gate や初期 `bHas*` 値だけで dummy / discard 側に流さず、実体として保持する。
2. `SerializeAvailabilityInfo()` の load 時には、serialized packed bits だけでなく、実際にロード済みの optional buffer data の有無も考慮して effective な `bHas*` 値を決定する。
3. Save 時には、実際に serialize される buffer data と保存される availability state が一致するようにする。
この修正では、CVar は「新規に depth-only / reversed index buffer support を要求するか」の条件としては使用しますが、既に serialized されている optional index buffer payload / availability を load 時に消す条件としては扱わないようにしています。
修正前:
DiffOnly 上で StaticMesh として報告された差分エントリ: 578
修正後:
DiffOnly 上で StaticMesh として報告された差分エントリ: 0
最終確認では、StaticMesh / `FRawStaticIndexBuffer::Serialize()` / optional index buffer payload 由来の差分は 0 件になり、一方で StaticMesh 以外の差分は引き続き検出されていました。そのため、DiffOnly の検出自体が無効になったわけではないと考えています。
確認したい点は以下です。
1. StaticMesh optional index buffer serialization / AvailabilityInfo reconstruction の経路は、同一条件の繰り返し Cook で決定的になることを意図されていますでしょうか。
2. Load 時には、`bEnableDepthOnlyIndexBuffer` / `bEnableReversedIndexBuffer` が false の場合でも、既に serialized されている optional index buffer payload を discard / `ClearMetaData()` せず保持すべきでしょうか。
3. AvailabilityInfo 再構築時に、serialized packed bits だけでなく、実際にロードされた optional buffer data の有無も考慮して effective な `bHas*` 値を決めることは妥当でしょうか。
4. CVar は、新規生成や platform support の可否には影響する一方で、既存 package からロードされた serialized optional index buffer payload / availability を消す条件としては扱わない、という方針は妥当でしょうか。
5. この方針により、runtime memory usage、platform-specific stripping、DDC compatibility、cooked package compatibility などに問題が発生する可能性はありますでしょうか。
6. Epic 側で Engine 修正として取り込んでいただくことは可能でしょうか。または、既存の推奨修正や workaround はありますでしょうか。
この問題は StaticMesh serialization path における Engine 側の Cook determinism 問題ではないかと考えています。ローカル patch で環境の StaticMesh Cook 出力は安定化できましたが、根治としては Epic 側の意図に沿った Engine 修正に揃えたいと考えています。
よろしくお願いいたします。
[Attachment Removed]