CreateCompatibleDCで実現するオフスクリーンバッファの仕組み
Windowsで滑らかな描画を行うには、フリッカやちらつきを抑止する工夫が欠かせません。その代表的な手法がオフスクリーンバッファであり、描画対象を一度メモリ上のビットマップに書き込み、完成した画像を画面へ転送する流れを取ります。このとき中心的な役割を担うのがCreateCompatibleDC関数です。デバイスコンテキスト(以下DC)に互換性を持つメモリDCを生成し、そこにビットマップを選択することで仮想的な描画領域を作り出せます。
メモリDCは、画面表示用のDCと同じ色深度やピクセル形式を備えるため、通常の描画APIをそのまま利用可能です。これにより、複雑なGDI処理でもCPU側で完結し、最終的な転送はBitBltやStretchBltで高速に実行できます。本稿では、このCreateCompatibleDCを使った基本的な実装パターンから、メモリリークを防ぐ後処理、ダブルバッファリングや透過描画への応用まで順に整理していきます。
CreateCompatibleDCの基本仕様
CreateCompatibleDCは、指定したDCと互換性を持つメモリデバイスコンテキストを生成するGDI関数です。プロトタイプはHDC CreateCompatibleDC(HDC hdc)で、戻り値は新規に作成されたメモリDCのハンドル、失敗時はNULLとなります。引数にNULLを渡すと、現在の画面と互換のあるDCを取得できるため、汎用のオフスクリーン領域として機能します。
生成直後のメモリDCは1×1ピクセルのモノクロビットマップを既定で保有しています。この状態ではまともな描画が行えないため、続いてCreateCompatibleBitmapで意図したサイズのビットマップを作成し、SelectObjectでDCに選択させます。これでメモリ上に「仮想キャンバス」が完成します。
注意点として、メモリDCはシステムリソースを消費します。1プロセスで大量に生成するとGDIハンドルが枯渇し、描画不能に陥るケースがあるため、用途に応じて使い回す設計が推奨されます。CreateCompatibleDCの仕様や関連APIの全体像は、Windows APIリファレンスに体系的にまとめられています。
オフスクリーンバッファで解決する問題
通常の描画では、ペンやブラシ、ポリゴンの輪郭を画面に送るたびにWM_PAINT内で完結させようとすると、描画途中の状態がそのまま可視化されます。これがフリッカやテアリングの正体です。オフスクリーンバッファは「完成した1枚の絵」を最終転送することで、この問題を根本から取り除きます。
バックグラウンドでの複雑な演算は、ビットマップに対して行った方が高速です。たとえばパステル調のグラデーションを数百回のSetPixelで塗る場合、画面DCへ直接書き込むと描画途中でユーザーが部分的な結果を見てしまいます。メモリDC上で塗り終えてからBitBltで一括転送すれば、体感速度も視認性も大きく改善されます。
メモリDCはGDIの座標変換やクリッピングの影響を受けにくいという特性もあります。子ウィンドウのリージョンやオーナードローなど、複雑な描画階層を持つUI部品では、一度メモリ側で正規化してから貼り付けることで、挙動を予測しやすくできます。
実装フローの典型パターン
実装は、おおむね次の流れに集約されます。最初にCreateCompatibleDCでhdcMemを取得し、CreateCompatibleBitmapで描画領域のビットマップhBmpを作ります。SelectObjectでhBmpをメモリDCに選択後、任意のGDI関数で描画します。最後にBitBltまたはStretchBltで画面DCへ転送すれば描画完了です。
後処理は確実に行わなければなりません。SelectObjectでメモリDCに既定のビットマップ(1×1モノクロ)を戻してから、DeleteObject(hBmp)とDeleteDC(hdcMem)を呼び出します。選択中のビットマップをDeleteObjectすると無効ハンドルの参照になるため、必ず外してから削除する順序を守ることが鉄則です。
サイズ変更に追従する必要がある場合は、WM_SIZEのタイミングで以前のビットマップを破棄し、新しいクライアント領域と同じ寸法のものを再生成します。毎フレーム再生成するのは無駄が多いため、ウインドウサイズが変わったときだけ更新する設計が効率的です。
メモリ管理とエラー処理
メモリDCおよびビットマップはGDIオブジェクトであり、プロセスが終了しても自動的には解放されません。WM_DESTROYや終了ハンドラで確実にDeleteDC、DeleteObjectを呼ぶことが大切です。例外経路でも解放漏れが出ないよう、ラッパ関数で一本化しておくと保守性が高まります。
サイズの見積もりも重要で、フルHD相当の32bppビットマップは約8MBを消費します。複数のバッファを同時に保持する設計では、物理メモリやGPU転送帯域を意識したチューニングが求められます。パレットモードの古いリソースを保有し続けるとGDIハンドルの上限(既定で10000個)に達する可能性もあるため、利用していないDCは即座に解放します。
マルチスレッド環境では、GDIオブジェクトは同一スレッドからの操作が前提です。描画スレッドを分ける場合は、CreateCompatibleDCをスレッド単位で生成するか、メッセージ経由での依頼に置き換える必要があります。スレッド間の排他制御については、InitializeCriticalSectionAndSpinCountの解説記事が参考になります。
実践的な活用シーン
最も典型的な用途はダブルバッファリングで、ウインドウ全体の描画を一度裏画面に書き、まとめてフリッカなしで表示する手法です。ゲームやチャート描画で多用され、Direct2D以前のGDIアプリケーションでは標準的なテクニックでした。
もう一つの活用例は、画像をオンデマンドで合成する場面です。サムネイル一覧では、メモリDC上に複数のアイコンを貼り付け、最後にまとめてPicture Controlに転送することで再描画コストを抑えられます。印刷プレビューでも、ページごとにビットマップへ描いてからスクロールに合わせて表示する設計が有効です。
αブレンドや半透明効果を実装する基礎としても、メモリDCは外せません。AlphaBlend APIは二つのDCを引数に取るため、ソース側とデスティネーション側を別々にメモリ化することで、CPU側の合成を柔軟に制御できます。最終的に効果確認済みのデータを画面に出力できるため、デバッグもしやすくなります。
実装時に意識したいチェック項目
- メモリDCを生成する前に、CreateCompatibleDCの戻り値がNULLでないか確認する
- ビットマップをDeleteObjectする前に、必ずSelectObjectで既定オブジェクトに戻す
- ウインドウリサイズ時は旧ビットマップを破棄してから再生成する
- WM_DESTROYなどの終了パスでもDeleteDCとDeleteObjectを忘れない
- 色深度や解像度が変わったケースを想定し、再生成のトリガ条件を整理する
オフスクリーンバッファは、CreateCompatibleDCを起点として、CreateCompatibleBitmap、SelectObject、BitBlt、DeleteDCの組み合わせで成立します。フリッカのない滑らかな描画と、CPU主体の柔軟性を両立させたい場面では、まず一度この基本パターンに沿って実装を組み上げてみるのが手堅い出発点になります。