TECHNICAL CASE NOTE D3D11タイトル · セーブサムネイル · DXGI / DXVK 解決済み・2タイトルで実機確認

セーブサムネイルが黒くなる

GetBuffer(index > 0) を、Present後も保持されているback bufferへ対応させる

SYMPTOMセーブ自体は成功するが、記録用の小さな画像だけが黒くなる
BOUNDARYD3D11アプリケーションとDXVKのswapchain実装の境界
RESULT同系統の2タイトルで、UIを含まない直前シーンを取得できた
§1

現象と再現条件

要旨: 64bitのD3D11アプリケーションをDXVKで動かすと、通常描画とセーブ処理は完了する一方、セーブスロットに表示されるサムネイルだけが黒くなった。保存ファイルではなく、swapchainから画像を読み戻す境界に原因があった。

通常描画は成立する

本編画面は継続して表示され、セーブ画面も操作できる。描画全体の停止ではない。

セーブデータは作成される

保存処理そのものは成功する。壊れているのは記録用画像の取得だけだった。

シーンを変えても黒い

特定の画像や動画に依存せず、セーブするたびに同じ症状を再現した。

非ゼロのbuffer indexで失敗する

セーブ操作と同時に、DXVKのGetImageが要求を拒否していた。

別問題との境界: 同じタイトル群では動画が白くなる問題も発生したが、原因はMetalとOpenGLの表示境界にあった。本稿では扱わず、「動画だけが白くなる」で分けている。
§2

セーブ時の要求をAPI境界まで追う

セーブ操作の直前からDXGIとDXVKのログを照合すると、毎回同じ一行が現れた。

err: D3D11: GetImage: BufferId > 0 not supported
ゲームエンジンGetBuffer(index > 0)
DXVKD3D11SwapChain::GetImage
読み戻し失敗サムネイルへ画素を供給できない

ゲーム側は、セーブ画面のUIが重なる前に表示していたシーンを使うため、IDXGISwapChain::GetBuffer(index > 0)を呼んでいた。観測したDXVK実装はback bufferを1枚だけ扱い、非ゼロのindexを未対応として拒否していた。

§3

同じ黒でも、CMVSとは原因が異なる

「黒いサムネイル」という症状名だけで既存修正を横展開せず、実際に通るAPIを基準に切り分けた。

OBSERVATION 01

保存処理や画像ファイルの破損ではない

セーブデータとサムネイル用ファイルは作成される。失敗点は、書き込み前の画素取得に限定できた。

OBSERVATION 02

動画表示の修正とは独立している

動画を表示できる構成へ変更した後も、非ゼロのbuffer要求は同じ場所で拒否された。動画の合成経路を直しても解消しない。

OBSERVATION 03

D3D9 / wined3dの修正は通らない

CMVSの事例はGetBackBuffer + LockRectを使うD3D9経路だった。本件はD3D11/DXVKのGetBuffer経路なので、修正対象が異なる。

ROOT CAUSE

DXVKが非ゼロのBufferIdを拒否していた

D3D11SwapChain::GetImageで要求が終端していた。画面を新しく保存する仕組みではなく、保持中のback bufferを返す互換処理が必要だった。

§4

修正前後の読み戻し経路

修正は履歴バッファを追加するものではない。Present後も次の描画まで内容が残るback buffer 0を、非ゼロの要求にも返す。

修正前後を比較
GetBuffer(index > 0)直前シーンを要求
DXGI_ERROR_UNSUPPORTED非ゼロを拒否
黒いサムネイル画素を取得できない

結果: セーブ処理は完了しても、サムネイルへ書き込む画素が得られない。

GetBuffer(index > 0)直前シーンを要求
back buffer 0を返すPresent後の内容を保持
正しいサムネイルUIのないシーン画像

結果: 次の描画まで残っている直前シーンを読み戻し、セーブスロットへ表示できる。

§5

DXVKへ限定した互換処理

Gcenx/DXVK-macOS 1.10.x系のソースを再ビルドし、D3D11SwapChain::GetImageの非ゼロ要求だけを変更した。

変更内容

従来はBufferId > 0なら即座に未対応を返していた。修正後は、DXVKが保持しているm_backBufferを画像取得元として返す。

パッチはx64/x32のD3D11 DLLとして再ビルドできる形で管理し、同梱ランタイムへ反映した。

BufferId > 0
- return UNSUPPORTED
+ image = m_backBuffer

Present後の内容
+ 次の描画まで利用
重要: この修正は複数世代のswapchain bufferを新たに保存する実装ではない。検証したタイトルが求める「直前に表示したシーン」と、DXVKが保持中のback buffer 0が一致する条件に限定した互換処理である。
§6

検証結果

実機で確認したこと

  • 1本目で、黒いサムネイルがUIを含まないシーン画像へ変わった。
  • 同じ経路を使う2本目でも、同じ修正でサムネイルを取得できた。
  • 通常描画と動画再生に回帰がないことを同じ検証時点で確認した。

再現可能性

  • 原因を示すログはセーブ操作ごとに再現した。
  • DXVKパッチとx64/x32の再ビルド手順をリポジトリに保持している。
  • 修正済みバイナリは実機結果と対応づけて管理している。
§7

現在の状態と適用範囲

現在の状態

  • D3D11/DXVKの黒サムネイル事例として、2タイトルで実機解決済み。
  • DXVKのパッチ、由来、再ビルド手順を現行リポジトリで管理している。
  • セーブ時の非ゼロbuffer要求だけを対象にした小さな差分である。

適用範囲

  • D3D11/DXVKでGetBuffer(index > 0)を使う経路に限定する。
  • 一般的なDXGIの複数buffer履歴を実装したものではない。
  • CMVSのD3D9 / wined3d事例には一般化しない。