TECHNICAL CASE NOTE Artemis Engine · 動画再生 · VMR-7 / DirectDraw 解決済み・2026-07-01実機確認

動画だけが白くなる

DirectDrawの前面描画を、Metal上のOpenGLビューへ表示する

§1

本編と動画で描画経路が分かれていた

Artemis Engine系の32bitタイトルで、本編は正常に描画できる一方、起動ロゴとオープニング動画だけが白くなった。動画終了後は本編へ戻るため、停止ではなく動画表示経路に限定された問題だった。

本編の描画そのものは正常

切り分け: 問題は本編のD3D9描画ではなく、動画だけが通る別経路にある

起動ロゴとオープニングがともに白い

切り分け: 単一ファイルの破損ではなく、動画表示の共通経路を疑う

動画フレームは前面描画まで到達する

観測: フレーム更新150回、可視領域への描画情報300回を記録した

通常の表示更新は0回

観測: DirectDrawの前面描画はwglSwapBuffersを通らなかった
描画構成: 本編はDXVK/Metal、動画はVMR-7→DirectDraw→wined3d/OpenGLを使用する。動画はMetalビュー上のOpenGL子ビューへ描けていたが、通常のswapを通らないため、その子ビューを表示する契機が発生していなかった。
§2

検証した修正案

前面描画の実経路、実行スレッド、swapの副作用を順に確認し、3案を棄却した。

案1

動画のフレーム更新を、描画の"通常の更新"経路でフックする棄却

既存のfront-buffer更新フックを利用する案を試した。

棄却理由: 診断ログでは一度も発火しなかった。動画はswapchain_frontbuffer_updatedを通らず、DirectDraw側で直接前面へ描いていた。
案2

今アクティブな描画の窓口から、隠れたビューを取得する棄却

呼び出しスレッドのOpenGL contextから対象ビューを取得する案を試した。

棄却理由: 8回の診断がすべてctx=0x0だった。DirectDrawのblitはwined3dのワーカースレッドで動き、そこにcurrent contextはなかった。
案3

素直に、通常の更新と同じ「表示入れ替え」を動画側からも呼ぶ棄却

動画更新後に通常と同じswapを呼ぶ案を検討した。

棄却理由: 動画は既にfront bufferへ描画済みだった。ここでswapすると、直前の動画フレームを古いback bufferで上書きする。
採用案

ウィンドウそのものから隠れたビューを辿り、入れ替えなしで表に出すだけ確定

DirectDrawから渡したウィンドウハンドル(HWND)を使い、winemac側で対象のOpenGL子ビューを解決する。

採用理由: 実行スレッドのcontextに依存せず、swapも行わない。既に描画済みの動画フレームを保持したまま子ビューだけを表示できた。
§3

swapの有無で表示結果を比較する

動画フレームは既にfront bufferへ描画されている。必要なのはbufferの入れ替えではなく、隠れているOpenGL子ビューの表示だった。

動画フレーム表に描画済み
表示を入れ替えるswap
古い絵で上書き— 消える —
真っ白
画面に残る状態
§4

起動ロゴの黒画面を分けて修正する

オープニングを表示できた後も、起動直後のロゴ動画は黒くなった。これは表示契機ではなく、OpenGL drawableの準備前に最初のフレームを描いていた別問題だった。

OpenGL子ビューはhiddenで生成され、画面上へ出るまでdrawable surfaceを持たない。ロゴの初期化待ちを省く設定では、surfaceの準備前に描画が始まり、FBO incompleteが発生していた。そこでopenGLTargetViewの生成時に子ビューを先に表示し、レンダー前にsurfaceを準備した。

計測によるタイミング変化: 診断ログを有効にするとロゴが映り、ログを外すと黒へ戻った。毎フレームの書き込み遅延がsurface準備時間を偶然確保していたため、最終確認はログを除いたbuildで実施した。早期表示後はFBO incompleteが6件から2件へ減り、ロゴを表示できた。
§5

調査手順

実際の起動条件を再現し、経路、スレッド、配備物、ログなしの最終結果を順に確認した。

本番と同じ起動条件でトレースを取る

launcherの設定を再現したハーネスを使い、動画フレーム更新と表示更新の回数を同じrunで記録した。

未発火のフックから実経路を絞る

既存フックが0回だったため、wined3dのswapchainではなくDirectDrawのfront-buffer更新へ観測点を移した。

修正案ごとに副作用を確認する

swapを追加する前に前面/背面bufferの状態を確認し、描画済みフレームを消す案を棄却した。

実際にロードされるruntimeを照合する

ソース、ビルド出力、app bundleは別物として扱い、ロード先のwinemac/ddrawをハッシュで照合した。

診断ログを外して再検証する

ログがタイミングへ影響することを確認したため、最終buildでは診断出力を除き、ロゴとオープニングを取り直した。

§6

観測結果

観測 A ── トレースの集計

動画のフレーム更新     ── 150 回(フルラン)
可視領域への描画情報   ── 300 回(非空)
通常の表示更新(swap)  ── 0 回
──────────────────────
∴ フレームは画面に届いているのに、
  表示更新の合図が一度も出ていない

動画フレームと描画領域は存在する一方、通常のswapは発生していなかった。

観測 B ── スレッドの診断

案2の実装で仕込んだ診断ログ:
  note_frontbuffer_flush #1 ctx=0x0
  note_frontbuffer_flush #2 ctx=0x0
  ... #3〜#8 すべて ctx=0x0

→ 描画の窓口は、動画を更新している
  スレッドにはカレントでない

動画更新スレッドにcurrent contextはなく、HWNDを基点にした解決が必要だった。

観測 C ── 修正前後の実機結果

修正前:
  オープニング動画  ── 真っ白
  起動ロゴ動画      ── 真っ白

修正後(一次):
  オープニング動画  ── ✅ 表示
  起動ロゴ動画      ── 黒に反転

修正後(最終・ログ無し):
  オープニング動画  ── 
  起動ロゴ動画      ── 
  効果音の乱れ      ── ✅ 解消
  セーブ/ロード等  ── ✅ 異常なし

overlay表示とsurface早期準備を分けて適用し、ログなしの実機runでロゴとオープニングを確認した。

§7

再利用できる知見

実際に発火する経路を測る

既存フックが論理上近くても、0回なら実経路ではない。回数計測から観測点を移す。

実行スレッドのcontextに依存しない

描画処理と更新通知が別スレッドなら、共有できるHWNDなどの識別子から対象を解決する。

表示済みbufferへswapを重ねない

front bufferへ直接描く経路では、通常のswapが描画済みフレームを消す可能性がある。

ログなしの最終条件を確認する

診断出力は競合や初期化待ちの時間を変える。観測buildと受入buildを分ける。

ソースではなくロード実体を照合する

修正の到達は、実際にロードされるwinemac/ddrawのハッシュで確認する。

§8

現在の状態と適用範囲

確認済み
  • 対象はArtemis 32bitで、本編がDXVK/Metal、動画がVMR-7→DirectDraw→wined3d/OpenGLの構成。
  • 2026-07-01の実機確認で、起動ロゴ、オープニング動画、効果音を確認した。
  • 再ビルドで修正を失わないよう、ddrawとwinemacの採用バイナリを再現性台帳で管理している。
制約
  • 動画白画面をすべて同じ原因へ一般化しない。GStreamer環境、registry、renderer、音声初期化には別の原因がある。
  • Metalビューを使わない構成では早期表示処理は構造上no-opだが、全エンジンの実機非回帰を完了したという意味ではない。
  • SEノイズの再発はmovie修正ではなく、別のdsound artifactの配備漏れだった。