feat: Vulkanバックエンド実装 - #99
Conversation
|
起動確認用の 確認できた項目:
PR本文の「Vulkan の実機起動確認は未実施」は本コメントで解消済みです。 |
Phase 2/3 実装完了: Vulkan バックエンドで実際の描画に対応スプライト・プリミティブの描画がVulkanで動作するようになりました。ピクセル単位の自動検証(6項目)が全てパスしています。 実装内容
回帰確認
未対応(今後のフェーズ)カスタムシェーダー / Material、マスク (stencil/alpha)、PieSprite、ポストプロセス、線幅>1、ImGui |
|
FrameBuffer のテクスチャ上下反転を修正しました。 原因: 修正: 検証項目に FrameBuffer の上下向きチェック(非対称コンテンツのピクセル検証)を追加し、8 項目全パスを確認済みです。 |
カスタムシェーダー・PieSprite・ポストプロセス対応完了ピクセル自動検証を 13 項目・2 フェーズに拡張し、全てパスしています。 実装内容
回帰確認
残タスクマスク系 (stencil/alpha) — オフスクリーンパスへのステンシルアタッチメント追加が必要で、次の大きめの作業。ほか: PieSprite のカスタムマテリアル、Material の Texture2D Uniform、線幅 >1、ImGui |
マスク対応完了 — 主要機能の移植が一巡ステンシルマスク・アルファマスクの両方式に対応しました。ピクセル自動検証は 17 項目に拡大し、全てパスしています。 実装内容
回帰確認
これで残るタスク(いずれも小物)PieSprite のカスタムマテリアル、Material の Texture2D Uniform、線幅 >1 (wideLines)、ImGui の Vulkan 対応、docs 更新 |
GL 機能パリティ完了GL バックエンドが持つ機能のうち Vulkan 未対応だった残り 3 点を実装しました。ピクセル自動検証は 20 項目、全てパスしています。 実装内容
スコープ外の整理(計画書に明記)
回帰確認
これで GL バックエンドの描画機能はすべて Vulkan でも動作します。 |
ImGui の Vulkan 対応完了
実装内容
検証
|
これまで Scene 派生クラスの自動登録は Assembly.GetEntryAssembly() のみを
走査していたため、ゲームをエンジン層とコンテンツ層の2プロジェクトに分割し、
シーンをエンジン側のアセンブリに置くと LoadScene が
「The scene "Xxx" is not registered.」で落ちていた。
PrometeAppBuilder に UseScenesFrom(Assembly) と UseScenesFrom<T>() を足し、
エントリアセンブリに加えて任意のアセンブリを探索対象にできるようにした。
PrometeApp.Create()
.UseScenesFrom(typeof(LoadingScene).Assembly)
.BuildWithVulkanDesktop(opts);
既定の挙動は変えていないので、単一アセンブリの構成には影響しない。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019eZmjGkVajSwKumgLsMwrA
Scene 派生クラスの自動登録がエントリアセンブリに限られることは、 これまでドキュメントに書かれていなかった。エントリアセンブリ外に シーンを置いたときの対処とあわせて記載する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019eZmjGkVajSwKumgLsMwrA
RenderCommandQueue.PushTrim がバックエンド非依存層で OpenGL 固有の 左下原点変換を行っていたため、キューは左上原点で座標を保持し、 Y反転は GLBeginTrim/GLEndTrimCommandRunner 側で行うよう変更。 Vulkan など左上原点のバックエンド追加への布石。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016A9ZxvQBn4vAu8Erah3rHr
- VulkanContext: instance / surface / device / swapchain / フレーム同期 (frames-in-flight=2)・クリアカラー描画・リサイズ時の再構築 - VulkanDesktopBackend / VulkanDesktopGameView / BuildWithVulkanDesktop() - テクスチャ・シェーダー等は暫定的に Headless 実装を流用 (Phase 2 で置換) - VULKAN_PORTING_PLAN.md: コードベース調査に基づく移植計画書 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016A9ZxvQBn4vAu8Erah3rHr
BuildWithVulkanDesktop() でウィンドウを起動し、クリアカラーの描画と 切り替え・自動終了までを確認する最小プログラム。実機で起動確認済み。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016A9ZxvQBn4vAu8Erah3rHr
- VulkanResourceManager: int IDテーブルでVkImage/ImageView/DescriptorSetを管理 (Texture2D.Handle 互換)、stagingバッファ経由アップロード - VulkanPipelineProvider: パイプライン遅延生成・キャッシュ、shadercによる GLSL450→SPIR-Vランタイムコンパイル (Silk.NET.Shaderc 追加) - VulkanRenderTextureProvider: オフスクリーンパスの中断/再開でBeginCapture のネストセマンティクスを再現、Resize対応 - VulkanContext: フレームライフサイクル (BeginFrame/EndFrame)、ターゲット スタック、per-frame頂点アリーナ、遅延破棄、ピクセル読み出し - ランナー: テクスチャバッチ (インスタンシング)、プリミティブ、トリム - VulkanScreenBlitter: フルスクリーントライアングルでスワップチェーンへブリット - スクリーンショット実装 (TakeScreenshot / SaveScreenshotAsync) - Promete.Experimental.Vulkan: ピクセル単位の自動検証 (6項目全パス) 未対応: カスタムシェーダー・マスク・PieSprite・ポストプロセス Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016A9ZxvQBn4vAu8Erah3rHr
FrameBuffer は GL の RT テクスチャが bottom-up 格納であることを前提に 子ノードを上下反転して補正しているが、Vulkan の RT は top-down のため 二重反転になっていた。RenderTexture 用の Texture2D に V 反転 UV を 持たせ、GL と同じ見え方に統一する。 検証: Experimental に FrameBuffer の上下向きピクセル検証を追加 (8項目全パス) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016A9ZxvQBn4vAu8Erah3rHr
dotnet run --project Promete.Example -- --vulkan で実験的な Vulkanバックエンドを試せる。ImGuiプラグインはOpenGL専用のため Vulkan時は無効化する。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016A9ZxvQBn4vAu8Erah3rHr
- SpirvReflector: SPIR-Vから uniform ブロックのメンバー名/オフセットを抽出する 最小リフレクタ (追加ネイティブ依存なし) - VulkanShaderManager / VulkanShaderFactory: GLSL450カスタムシェーダーの コンパイルと ShaderProgram.Handle 互換のIDテーブル管理 - VulkanMaterialSystem: Material の名前ベース Uniform をリフレクション結果に 基づき per-material UBO (set=1, binding=0) へ書き込み - スプライトのカスタムマテリアル描画パス (texture_instanced 互換レイアウト規約) - VulkanDrawPieTextureCommandRunner: push constant (MVP/tint/角度) で扇形描画 - VulkanScreenBlitter: ピンポンバッファによるポストプロセスチェーン - スクリーンショットをポストプロセス適用後の最終ブリット元から読むよう変更 - Experimental: 検証を13項目に拡張 (PieSprite/カスタムマテリアル/色反転 ポストプロセスの2フェーズ検証) — 全パス Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016A9ZxvQBn4vAu8Erah3rHr
- オフスクリーンパス・RenderTexture にステンシルアタッチメントを追加 (D24S8/D32S8 をフォーマット照会で選択、パス中断/再開をまたいで保持) - パイプラインにステンシルバリアント (None/WriteMask/TestEqual) を追加し、 ランナーは StencilMaskActive に応じて選択 - ステンシルマスク: vkCmdClearAttachments でクリア → マスク形状書き込み (カラー書き込み無効 + Always/Replace) → 子を Equal テストで描画 - アルファマスク: VulkanMaskedContainerHelper が子を RenderTexture へ サブレンダリングし、masked シェーダーで content×mask 合成 (set0=content, set1=mask として既存のテクスチャ毎ディスクリプタを流用) - Experimental: マスク両方式の検証を追加、全17項目パス Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016A9ZxvQBn4vAu8Erah3rHr
GL バックエンドとの機能パリティを完成させる: - SpirvReflector: サンプラー変数 (名前/set/binding) の抽出を追加 - カスタムパイプラインレイアウトをシェーダー毎に生成 (追加テクスチャのセット数をリフレクションから決定) - Material の Texture2D Uniform: set=2 以降の同名 sampler2D に 既存のテクスチャ毎ディスクリプタセットをバインド - VulkanDrawPieTextureCommandRunner / VulkanDrawPrimitiveCommandRunner に カスタムマテリアル描画パスを追加 - Experimental: 検証を20項目に拡張 — 全パス スコープ外と決定: 線幅>1 (wideLines非依存の展開は将来検討)、ImGui Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016A9ZxvQBn4vAu8Erah3rHr
- VulkanImGuiController: Vulkan用のImGuiレンダラと入力処理を実装
- フォントアトラスは VulkanResourceManager に登録し、ImGui の TexID として
Promete のテクスチャ ID をそのまま使用 (ImGui.Image に Texture2D.Handle が渡せる)
- frames-in-flight 対応の頂点/インデックスバッファ、シザー・push constant射影
- Silk.NET.Input からのキーボード/マウス/ホイール入力を ImGui IO へ変換
- スワップチェーンパスを EndFrame まで開放し、PostRender でオーバーレイを
直接記録できるように変更 (GLの描画順と同一セマンティクス)
- ImGuiPlugin: バックエンドで GL/Vulkan のコントローラを自動選択
- InternalsVisibleTo Promete.ImGui を追加
- Example: Vulkan時のImGuiPlugin無効化を解除
- Experimental: ImGuiデモウィンドウをオーバーレイした状態で20項目検証全パス
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A9ZxvQBn4vAu8Erah3rHr
e27071d to
2d412a9
Compare
There was a problem hiding this comment.
Pull request overview
Vulkan デスクトップバックエンドを追加し、OpenGL 相当の描画機能・ImGui・検証基盤を整備する PR です。
Changes:
- Vulkan のリソース、パイプライン、シェーダー、描画ランナーを実装
- トリム座標を左上原点へ統一し、GL 側の変換を修正
- ImGui、validation、サンプル、テスト、複数アセンブリの Scene 登録を追加
Reviewed changes
Copilot reviewed 69 out of 69 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
VULKAN_PORTING_PLAN.md |
移植計画と進捗を記録 |
Promete/VulkanDesktop/VulkanDesktopAppExtension.cs |
Vulkan ビルド API を追加 |
Promete/Backends/Vulkan/VulkanDesktopBackend.cs |
Vulkan バックエンドを構成 |
Promete/Backends/Vulkan/VulkanDesktopGameView.cs |
Vulkan 用 GameView とスクリーンショットを実装 |
Promete/Graphics/Rendering/Vulkan/VulkanContext.cs |
Vulkan コンテキストと同期処理を実装 |
Promete/Graphics/Rendering/Vulkan/VulkanPipelineProvider.cs |
パイプライン生成・キャッシュを実装 |
Promete/Graphics/Rendering/Vulkan/VulkanResourceManager.cs |
テクスチャリソースを管理 |
Promete/Graphics/Rendering/Vulkan/VulkanTextureFactory.cs |
Vulkan テクスチャ生成を実装 |
Promete/Graphics/Rendering/Vulkan/VulkanShaderCompiler.cs |
shaderc コンパイルを実装 |
Promete/Graphics/Rendering/Vulkan/VulkanShaderManager.cs |
シェーダーモジュールを管理 |
Promete/Graphics/Rendering/Vulkan/VulkanShaderFactory.cs |
ShaderProgram の Vulkan 実装を追加 |
Promete/Graphics/Rendering/Vulkan/SpirvReflector.cs |
SPIR-V リフレクションを実装 |
Promete/Graphics/Rendering/Vulkan/VulkanMaterialSystem.cs |
Uniform と追加テクスチャを適用 |
Promete/Graphics/Rendering/Vulkan/VulkanFrameArena.cs |
フレーム用動的バッファを実装 |
Promete/Graphics/Rendering/Vulkan/VulkanRenderTarget.cs |
Vulkan RT リソースを定義 |
Promete/Graphics/Rendering/Vulkan/VulkanRenderTextureProvider.cs |
RenderTexture を実装 |
Promete/Graphics/Rendering/Vulkan/VulkanScreenBlitter.cs |
ポストプロセスと画面転送を実装 |
Promete/Graphics/Rendering/Vulkan/VulkanMaskedContainerHelper.cs |
マスク描画を実装 |
Promete/Graphics/Rendering/Vulkan/IVulkanInstanceHook.cs |
Instance 拡張フックを公開 |
Promete/Graphics/Rendering/Vulkan/Runners/VulkanDrawTextureBatchedCommandRunner.cs |
スプライト描画を実装 |
Promete/Graphics/Rendering/Vulkan/Runners/VulkanDrawPrimitiveCommandRunner.cs |
プリミティブ描画を実装 |
Promete/Graphics/Rendering/Vulkan/Runners/VulkanDrawPieTextureCommandRunner.cs |
PieSprite 描画を実装 |
Promete/Graphics/Rendering/Vulkan/Runners/VulkanTrimCommandRunners.cs |
Vulkan シザーを実装 |
Promete/Graphics/Rendering/Vulkan/Runners/VulkanMaskCommandRunners.cs |
マスクコマンドを実装 |
Promete/Resources/shaders/vulkan/texture_instanced.vert |
スプライト頂点シェーダー |
Promete/Resources/shaders/vulkan/texture_instanced.frag |
スプライト断片シェーダー |
Promete/Resources/shaders/vulkan/stencil_mask.frag |
ステンシルマスクシェーダー |
Promete/Resources/shaders/vulkan/primitive.vert |
プリミティブ頂点シェーダー |
Promete/Resources/shaders/vulkan/primitive.frag |
プリミティブ断片シェーダー |
Promete/Resources/shaders/vulkan/pie.vert |
PieSprite 頂点シェーダー |
Promete/Resources/shaders/vulkan/pie.frag |
PieSprite 断片シェーダー |
Promete/Resources/shaders/vulkan/masked.vert |
マスク合成頂点シェーダー |
Promete/Resources/shaders/vulkan/masked.frag |
マスク合成断片シェーダー |
Promete/Resources/shaders/vulkan/blit.vert |
全画面転送頂点シェーダー |
Promete/Resources/shaders/vulkan/blit.frag |
全画面転送断片シェーダー |
Promete/PrometeApp.cs |
外部アセンブリの Scene 登録を追加 |
Promete/Promete.csproj |
Vulkan 依存関係とリソースを追加 |
Promete/Graphics/Rendering/RenderCommandQueue.cs |
トリム計算を左上原点へ統一 |
Promete/Graphics/Rendering/Commands/TrimCommands.cs |
トリム座標の文書を更新 |
Promete/Graphics/Rendering/GL/Runners/GLBeginTrimCommandRunner.cs |
GL 開始シザーで Y 反転 |
Promete/Graphics/Rendering/GL/Runners/GLEndTrimCommandRunner.cs |
GL 終了シザーを復元 |
Promete.Vulkan.Validation/Promete.Vulkan.Validation.csproj |
Validation パッケージを追加 |
Promete.Vulkan.Validation/VulkanValidationHook.cs |
Debug Utils フックを実装 |
Promete.Vulkan.Validation/VulkanValidationExtension.cs |
Validation 登録 API を追加 |
Promete.Vulkan.Validation/VulkanValidationMessage.cs |
Validation メッセージ型を追加 |
Promete.ImGui/Promete.ImGui.csproj |
ImGui パッケージ版を更新 |
Promete.ImGui/ImGuiPlugin.cs |
GL/Vulkan コントローラを選択 |
Promete.ImGui/IImGuiController.cs |
ImGui 共通インターフェースを追加 |
Promete.ImGui/VulkanImGuiController.cs |
Vulkan ImGui 描画を実装 |
Promete.Experimental.Vulkan/Promete.Experimental.Vulkan.csproj |
Vulkan 検証プロジェクトを追加 |
Promete.Experimental.Vulkan/Program.cs |
検証アプリの起動処理 |
Promete.Experimental.Vulkan/MainScene.cs |
ピクセル検証シーンを実装 |
Promete.Example/Promete.Example.csproj |
Validation プロジェクトを参照 |
Promete.Example/Program.cs |
Vulkan 起動フラグを追加 |
Promete.Example/MainScene.cs |
使用バックエンドを表示 |
Promete.Example/Kernel/DemoKernel.cs |
Vulkan 選択状態を保持 |
Promete.Example/examples/debug/VulkanShaderReflectionDebugScene.cs |
シェーダー境界検証シーン |
Promete.Example/examples/debug/VulkanMaterialSlotLeakDebugScene.cs |
Material 枯渇再現シーン |
Promete.Example/examples/debug/VulkanMaskRestoreDebugScene.cs |
マスク例外再現シーン |
Promete.Example/examples/debug/VulkanFrameArenaGrowthDebugScene.cs |
Arena 拡張検証シーン |
Promete.Example/examples/debug/TrimClampDebugScene.cs |
トリム境界検証シーン |
Promete.Test/VulkanDemoShaderCompileTests.cs |
Vulkan GLSL コンパイルを検証 |
Promete.Test/SpirvReflectorTests.cs |
SPIR-V パーサーを検証 |
Promete.Test/TrimCommandQueueTests.cs |
トリム計算を検証 |
Promete.Test/TrimSceneShapeTests.cs |
入れ子トリムを検証 |
Promete.Test/SceneRegistrationTests.cs |
外部 Scene 登録を検証 |
Promete.Docs/src/content/docs/guide/extends/rendercommand.md |
Runner シグネチャを訂正 |
docs-llm.md |
Scene 登録 API を文書化 |
Promete.sln |
Vulkan 関連プロジェクトを追加 |
Suppressed comments (2)
Promete/Graphics/Rendering/Vulkan/VulkanMaterialSystem.cs:67
Materialは Uniform 値を含む可変のGetHashCode()を実装しているため、このキーを登録後にmaterial["uTime"]などを更新すると既存エントリを検索できなくなります。通常のアニメーションでも毎フレーム新しい UBO/descriptor set が確保され、固定 1024 セットのプールが枯渇するので、参照同一性に基づく安定したキーを使ってください。
Promete/Graphics/Rendering/Vulkan/VulkanMaterialSystem.cs:221- 新規スロットのホストメモリは初期化されておらず、
Applyは Material に指定されたメンバーだけを書き込みます。Uniform ブロック内の未指定メンバーが未定義値になり、GL のリンク直後のゼロ初期値とも挙動が一致しません。マップ直後にブロック全体をゼロクリアしてください。
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| private void DestroyBuffer(Buffer buffer, DeviceMemory memory) | ||
| { | ||
| _ctx.Vk.DestroyBuffer(_ctx.Device, buffer, null); | ||
| _ctx.Vk.FreeMemory(_ctx.Device, memory, null); |
| private readonly VulkanContext _ctx; | ||
| private readonly VulkanShaderManager _shaders; | ||
| private readonly VulkanResourceManager _resources; | ||
| private readonly Dictionary<(Material Material, int Slot), MaterialSlot> _slots = []; |
| var opcode = words[index] & 0xFFFF; | ||
| var wordCount = (int)(words[index] >> 16); | ||
| if (wordCount == 0) | ||
| break; |
| Binding = 0, | ||
| DescriptorType = DescriptorType.CombinedImageSampler, | ||
| DescriptorCount = 1, | ||
| StageFlags = ShaderStageFlags.FragmentBit, |
| SpirvReflector.SamplerBinding? sampler = null; | ||
| foreach (var s in entry.Samplers) | ||
| { | ||
| if (s.Set >= 2 && s.Name == name) | ||
| { |
| queue.PushScope(); | ||
| foreach (var child in sorted) | ||
| app.CollectNode(child, queue, renderContext); | ||
| queue.PopScopeAndFlush(); |
| private static void DestroyBuffer(VulkanContext ctx, VkBuffer buffer, DeviceMemory memory) | ||
| { | ||
| if (buffer.Handle == 0) | ||
| return; | ||
| ctx.Vk.DestroyBuffer(ctx.Device, buffer, null); | ||
| ctx.Vk.FreeMemory(ctx.Device, memory, null); | ||
| } |
| foreach (var slot in _slots.Values) | ||
| { | ||
| vk.DestroyBuffer(device, slot.Buffer, null); | ||
| vk.FreeMemory(device, slot.Memory, null); | ||
| } |
| /// 全描画をスクリーンサイズの <see cref="RenderTexture"/> にキャプチャし、 | ||
| /// ポストプロセスマテリアルを適用した後、スワップチェーンイメージへブリットするクラスです。 | ||
| /// </summary> | ||
| internal sealed class VulkanScreenBlitter : IScreenBlitter |
| // インスタンスフックが登録されていれば使う (バリデーションレイヤー等) | ||
| _app.TryGetPlugin<IVulkanInstanceHook>(out var instanceHook); | ||
| _context = new VulkanContext(_nativeWindow, instanceHook); |
概要
OpenGL 3.3 のみだったバックエンドに Vulkan を追加する対応。コードベースと Promete.Docs をファクトチェックした上で移植計画を立案し、基盤整備から実装を進めた結果、GL バックエンドの描画機能はすべて Vulkan でも動作する状態まで到達しました。
詳細な調査結果・課題一覧・フェーズ計画は
VULKAN_PORTING_PLAN.mdを参照してください。変更内容
計画書・基盤整備
VULKAN_PORTING_PLAN.md: GL依存 16ファイル約2,600行の分析、抽象の漏れ 9 件、技術選定(Silk.NET.Vulkan / shaderc / SPIRVリフレクション)、フェーズ計画RenderCommandQueue.PushTrimが GL 固有の左下原点Y反転をバックエンド非依存層で行っていた問題を修正。トリム座標を左上原点に統一し、Y反転はGLBeginTrim/GLEndTrimCommandRunner側へ移動(Vulkan は左上原点のためそのまま使える)rendercommand.mdのCommandRunner.ExecuteシグネチャVulkan バックエンド実装
VulkanContext: instance(DEBUG時 validation layers)/ surface / physical device 選択 / logical device / swapchain / レンダーパス / frames-in-flight=2 の同期・リサイズ時のスワップチェーン再構築VulkanResourceManager:Texture2D.Handle(int) 互換のリソーステーブルで VkImage / ImageView / DescriptorSet を管理VulkanPipelineProvider: パイプライン遅延生成+キャッシュ、標準シェーダー (Vulkan GLSL 450) を shaderc でランタイムコンパイルSpirvReflector: SPIR-V バイナリから uniform ブロック・サンプラー変数を抽出する自前の最小リフレクタ(SPIRV-Cross のネイティブ依存なし)VulkanShaderManager/VulkanShaderFactory/VulkanMaterialSystem: カスタムシェーダーの shaderc コンパイルと、リフレクション結果に基づく Material の Uniform 解決(uniform ブロック・Texture2D 双方)VulkanRenderTextureProvider: レンダーパスの中断/再開によりBeginCaptureのネストセマンティクスを再現。FrameBuffer の上下反転は V反転 UV で GL と挙動を統一VulkanImGuiController: Silk.NET 公式コントローラが OpenGL 専用のため自作。ImGui.ImageにTexture2D.Handleをそのまま渡せる形で統一VulkanDesktopBackend/VulkanDesktopGameView/BuildWithVulkanDesktop()Promete.Example:--vulkanフラグでバックエンド選択可能にPromete.Experimental.Vulkan: 実機起動・ピクセル自動検証用プロジェクトスコープ外(計画書に明記・今後の課題)
検証
dotnet test Promete.Test: 147件全合格(トリム座標変更含め回帰なし)備考
shader.mdのカスタム頂点シェーダー例が v2 のインスタンシング頂点レイアウト(location 2〜7 /uProjection)と不整合の疑いあり。計画書のリスク 2 に記載。GL 側で仕様確定を推奨🤖 Generated with Claude Code