Skip to content

Job System移行 Step2-3: ポリゴン分割のSoA/Job System + Burst化 - #35

Merged
CyTakayukiKiyohara merged 8 commits into
mainfrom
feature/job-system-step3
Jul 31, 2026
Merged

Job System移行 Step2-3: ポリゴン分割のSoA/Job System + Burst化#35
CyTakayukiKiyohara merged 8 commits into
mainfrom
feature/job-system-step3

Conversation

@CyTakayukiKiyohara

Copy link
Copy Markdown
Collaborator

概要

デカール生成パイプライン(skinning / broadphase / clip / build)を ThreadPool ワーカー1本から Unity Job System(SoA + IJobParallelFor)+ Burst へ移行します。Job System 移行計画(JobSystemMigrationPlan.md)の Step 2・Step 3 を含みます(Step 0/1 は #32/#33 でマージ済み)。

含まれるコミット

  • Step 2(8d125c1, 24d51b8): メッシュアップロードの MeshData API 化、タンジェント計算のワーカー移動
  • Step 3a(f1ea701): ポリゴン分割を SoA + Unity Job System 化(Burst 無効)
  • Step 3c(adcac77): ジョブの Burst 有効化
  • ベンチマーク(b05068f): クリーン計測用シーン + Burst 稼働 probe

主な変更(Step 3)

  • ConvexPolygon(class)/ Line / BroadPhaseConvexPolygonsDetection を廃止し、blittable な SoA(ReceiverConvexPolygonsMesh: NativeArray<float3> 等)へ再設計
  • Component 参照を全種別横断のグローバル componentIndex に統一(ジョブからマネージド参照を排除)
  • per-vertex world normal を除去(出力に不使用のデッドデータ)、Line 廃止でエッジをオンザフライ計算 → per-vertex 252B→68B でメモリ削減
  • ジョブチェーン: skinning+broadphase(並列)→ clip(並列)→ build(serial・オフメイン)。コルーチンのフラグポーリングを 2 段 JobHandle.IsCompleted ポーリングへ置換
  • ThreadPool 廃止。追記が全てジョブ完了後の main スレッドなので旧ロールバック機構は不要化し削除
  • 受け消滅時の use-after-free 対策(ReceiverConvexPolygonsMesh.InUse ピン)

計測(実機 Pixel 8a / IL2CPP、probe 付き有効 A/B)

フェーズ 効果
Step 3a(Job 並列化・Burst 無効) 旧単一スレッド → マルチコア並列(本命の勝ち筋)
Step 3c(Burst 有効化) worker さらに ~1.58x(clip 1.48x / build 1.65x)
  • エディタ Mono 比では worker 6.3x だが、Mono ベースラインのため実機(IL2CPP)より大きく出る点に注意。
  • FloatMode 未指定(IEEE 厳密)で結果は Burst 無効と一致、デモの見た目は不変。
  • Burst 稼働は Demo_Benchmark[BurstDiscard] probe で確定(ON ビルド=YES / OFF ビルド=NO)。Burst AOT 無効化は Project Settings > Burst AOT Settings(Jobs メニューのトグルはエディタ JIT のみで AOT ビルドに効かない)。

破壊的変更(→ メジャー 2.0.0 相当。バージョン bump は 3d で対応)

  • 依存追加: com.unity.mathematics / com.unity.burst(本プロジェクトは元々推移的に保持)
  • 最小 Unity を 2020.3 → 6000.0(6.x)へ
  • public API 削除: ConvexPolygon / Line / BroadPhaseConvexPolygonsDetection / ConvexPolygonInfo

検証

  • EditMode テスト全緑。数値ロジック(球-三角形距離 / クリップ / タンジェント)は移植時に旧実装との差分テストで等価性を担保し、旧削除後はスタンドアロン(プロパティ + 既知値)へ移行。
  • デモ 01–04 の見た目差分なし、ジョブセーフティ(editor collections checks)健全。
  • エディタ + 実機の広範な計測。Demo_Benchmark は決定論的クリーン計測(同一受け・バインドポーズ固定・同一デカール・各 Launch 前にリセット)を提供。

フォローアップ(このPRに含まない)

  • Step 3d: package.json を 2.0.0 へ、残課題 Feature/doc #2(Demo03 の計測ログ常時 ON)/Feature/gc pools #3(AirStickerPerformanceLog の扱い)、README / README_DEVELOPERS(EN/JA)/ CHANGELOG 更新。
  • Step 0 の残計測(静的メッシュ / テレイン / 連続 Launch)。

🤖 Generated with Claude Code

CyTakayukiKiyohara and others added 8 commits July 31, 2026 15:17
- DecalMeshの頂点/インデックスバッファ構築をMesh.AllocateWritableMeshData /
  ApplyAndDisposeWritableMeshDataへ移行し、複数回のSet*呼び出しを単一のApplyに置き換え
- スキンメッシュはPosition/Normal/Tangent=stream 0、TexCoord0=stream 1、
  BlendWeight/BlendIndices=stream 2の3ストリーム構成(Unityの必須制約。
  単一ストリームだとSetVertexBufferParamsが拒否しメッシュが崩壊する)
- スキニングウェイトはboneWeightsプロパティではなく頂点属性として格納し、
  代入時の頂点バッファ再レイアウトを排除
- Mesh.RecalculateTangents()を廃止し、DecalMeshTangentCalculator(Lengyel法)を新設。
  ワーカースレッド内で追記分のみ差分計算するため、メインスレッドのコストが消え
  デカール積み重ね時も計算量が頂点総数に比例しない
- インデックスフォーマットを頂点数に応じてUInt16/UInt32自動選択
  (旧実装は暗黙のUInt16固定で65,535頂点超で壊れる潜在バグがあった)
- 計測結果(エディタMono・入力8191/生存4849ポリゴン・2回目Launch):
  アップロード最大0.11→0.09ms、タンジェント計算のワーカー追加分0.08ms、
  ワーカー合計は同水準(9.63ms vs 8.65ms)でリグレッションなし

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
デカール分割処理(skinning/broadphase/clip/build)をThreadPoolワーカー1本から
Unity Job System(IJobParallelFor)へ移行。段階式のため今回はBurst無効で、
Burst有効化(依存追加)は次ステップ3cに分離。設計基準はUnity 6.x前提。

## データモデル
- ConvexPolygon(class)/Line/BroadPhaseConvexPolygonsDetectionを廃止し、
  blittableなSoA(ReceiverConvexPolygonsMesh: NativeArray<float3>等)へ再設計
- Component参照を全種別横断のグローバルcomponentIndexに統一(ジョブからマネージド参照を排除)
- per-vertexのworld normalを除去(出力に不使用のデッドデータ)、Lineも廃止し
  エッジはリング隣接からオンザフライ計算 → per-vertex 252B→68Bでメモリ削減

## ジョブチェーン
- SkinningBroadPhaseJob(三角形単位・並列): スキニング+faceNormal+broadphaseカリング
- ConvexPolygonClipJob(三角形単位・並列): 6平面クリップ(SplitAndRemoveByPlane移植)
- DecalMeshBuildJob(serial・オフメイン): fan展開+UV+zOffset+タンジェント計算
- コルーチンのフラグポーリングを2段JobHandle.IsCompletedポーリングへ置換

## 基盤・その他
- package.json: unity 2020.3→6000.0、com.unity.mathematics依存追加
- 受け消滅時のuse-after-free対策(InUseピン)。追記がジョブ完了後のmainスレッドなので
  旧ロールバック機構は不要化し削除
- 差分テストで移植の数値等価性を検証(距離・クリップ・タンジェント。旧削除後スタンドアロン化)

## 計測(大きめスキンメッシュ・2回目Launch)
- エディタMono: ワーカー計算 9.63ms→1.98ms (約4.9x、Burst無しの並列化のみ)
- 実機Pixel 8a(IL2CPP) steady: ワーカー計算 約2.3ms。見た目差分なし・リグレッションなし

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Step 3a(Job並列化・Burst無効)の上に、3ジョブへ[BurstCompile]を付与しBurstを有効化。
- SkinningBroadPhaseJob / ConvexPolygonClipJob / DecalMeshBuildJob に [BurstCompile]
- package.json に com.unity.burst 依存、asmdef に Unity.Burst 参照を追加

計測(probe付き有効A/B、Demo_Benchmark、同一ワークロード確認済み):
- 実機 Pixel 8a / IL2CPP steady: worker 7.56ms → 4.80ms (~1.58x。clip 1.48x / build 1.65x)
- エディタ Mono steady: worker 2.02ms → 0.32ms (6.3x。Monoベースライン比のため実機より大きく出る)
- FloatMode未指定(IEEE厳密)で結果はBurst無効と一致、見た目不変

Burst稼働は [BurstDiscard] probe で確定(ONビルド=YES / OFFビルド=NO)。
Burst AOT無効化は Project Settings > Burst AOT Settings で行う(Jobsメニューのトグルは
エディタJITのみでAOTビルドに効かない)点に注意。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Demo03のエージングテスト(毎回別デカール)は計測を濁らせるため、決定論的な
ベンチマークを追加。同一受け(バインドポーズ固定)に同一デカールを、各Launch前に
リセットして繰り返すので、全Launchが同一のclip/build仕事量になり Burst on/off を
厳密比較できる。

- Demo_Benchmark シーン(最小構成、実行時にSystem/カメラ/ライト/受けを自動生成)
- AirStickerBenchmark.cs: 自己完結ベンチ + [BurstDiscard] による Burst稼働 probe
  ([AirSticker][Bench] Burst active: YES/NO をログ)
- 再生と同時に6回Launch自動実行、launch #2+ を steady-state として読む

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Step 3a/3c のコミット済み状態を反映し、残課題#1/#4がStep 3再設計で
自然解消したこと、残るは#2/#3を3dで対応することを追記。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex CLIによるセルフレビューで検出した3件を修正(再レビューで指摘なしを確認)。

- (blocker) AirStickerSystem.OnDestroy がジョブ実行中にNativeArrayを破棄しuse-after-free:
  DecalMeshJobPipeline._lastScheduledHandle を追加し Dispose() 先頭で Complete。
  OnDestroy は pipeline.Dispose(=ジョブ完了) → pool.DisposeAll の順で source も安全に破棄。
- (major) 部分フィルで未初期化SoAが登録される: BuildFromReceiverObject 末尾で
  _writeTriangleCursor == triangleCount を検証し、不一致なら result.Dispose してキャンセル。
- (major) componentIndex不整合(meshFilter index vs meshRenderer基準のComponentByIndex):
  メッシュ経路をレンダラ駆動化(FillFromMeshRenderers/GetNumPolygonsFromMeshRenderers)し
  count/fillを一致、meshFilters引数を除去。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	Assets/AirSticker/Runtime/Scripts/Core/DecalMesh.cs
#	JobSystemMigrationPlan.md
@CyTakayukiKiyohara
CyTakayukiKiyohara merged commit 60b516e into main Jul 31, 2026
@CyTakayukiKiyohara
CyTakayukiKiyohara deleted the feature/job-system-step3 branch July 31, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant