やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

GDI/GDI+/DirectX 描画アーキテクチャの切替2

GDI、GDI+、DirectXの描画を実装した。 実行中に切り替えることができる。

前回は切替ができなかった。

今回はキーボードの 1, 2, 3 キーを押下するとそれぞれの描画アーキテクチャに切り替わる。

入手先

GitHub MEGA

イメージ

ソースコード一覧

ソース一覧

実行結果

初期表示

GDI

GDI

GDI

GDI+

GDI+

DirectX9

DirectX9

課題

IPartWndProcに、以下のインタフェースを追加した。 これは相応しくないかもしれない。 でも、面倒だから一旦はこういうことにしておいた。 いずれ改善したい。

class IPartWndProc {
    void Initialize() = 0;
    void Finalize() = 0;
}

悪い理由

追加したInitialize()Finalize()を使っているのは GdiWndProc, GdiPlusWndProc, DirectXWndProc の描画アーキテクチャclassだけ。 InitializeWndProc, KeyboardWndProc, では使っていない。

本来Initialize(), Finalize()のインタフェースを継承したclassはポリモーフィズムで一括して実行するべきだと思う。そうでないと意味がない。でも、やっていない。 そもそも、InitializeWndProc, KeyboardWndProc, ではInitialize(), Finalize()を使う必要がない。

実際にInitialize(), Finalize()を使っているのは、描画アーキテクチャclassだけ。 つまり「描画アーキテクチャを切り替えるときの初期化と終了処理」として利用されている。 「WindowMessageを処理するための準備としての初期化」ではない。 よって、IPartWndProcのインタフェースとして定義するのはふさわしくないと思う。

IPartWndProcとしてのInitialize, Finalizeのタイミングは描画アーキテクチャを切り替えるときではない。 おそらく、プログラムが起動したら1回だけ。 もし他にも違うタイミングの初期化や終了処理が必要なら、それぞれにインタフェースclassを設けるのが理想的だと思う。

改善案

描画アーキテクチャ用のインタフェースを継承するのが良いと思う。 IPartWndProcではなく、IDrawWndProcのようなclassでインタフェースを定義する。

以下の4パターンが考えられる。 今回は手抜きをしてパターン1で実装した。 理想はパターン3。 パターン2はメリットがなさそう。 パターン4は可能なのか検討できていない。

パターン1

class GdiWndProc : public IPartWndProc {};

class IPartWndProc {
public:
    virtual LRESULT CALLBACK PartWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL* pIsReturn) =
    virtual void Initialize() = 0;
    virtual void Finalize() = 0;
};

問題点

Initialize, Finalize, の使い方がふさわしくない。 現在は描画アーキテクチャを変更するときに使用している。 それなら、IPartWndProcでなく、IDrawWndProcなど、描画用インタフェースとして実装すべき。

パターン2

class GdiWndProc : public IPartWndProc, IInitializable, IFinalizable {};

class IPartWndProc {
public:
    virtual LRESULT CALLBACK PartWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL* pIsReturn) = 0;
};

class IInitializable {
public:
    virtual void Initialize() = 0;
};

class IFinalizable {
public:
    virtual void Finalize() = 0;
};

問題点

このインタフェースは、どの部分で、どういう意図で使われるのか。 それが判別できると、可読性が上がると思う。 どこでも自由な形で使えてしまえそうなパターン2では、汎用性が高すぎて使用箇所も用途もバラバラになってしまいそう。

パターン3

class GdiWndProc : public IDrawWndProc {}

class IDrawWndProc
{
    virtual LRESULT CALLBACK PartWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL* pIsReturn) = 0;
    virtual void Initialize() = 0;
    virtual void Finalize() = 0;
}

問題点

PartWndProc関数が重複してしまう。 IPartWndProcとIDrawWndProcの両classにPartWndProc関数があることになる。 両classを継承することはできない。 見っともない。

パターン4

class GdiWndProc : public IDrawWndProc {}

class IDrawWndProc
{
    virtual void Draw() = 0;
    virtual void Initialize() = 0;
    virtual void Finalize() = 0;
}

問題

WindowsMessageが受け取れなくなってしまう。 描画アーキテクチャごとに色々と差異があるのに可能なのか。判断できない。

  • DirectXの初期化にはhWndが必要
    • CreateWindow()の後でやる必要があるがどうやって実装するか
      • 現状でも問題ないかも
  • GDI+とDirectXではWM_ERASEBKGNDのときにreturnして自動再描画を無効にしないとちらつく
    • でもGDIは必要ないっぽい

おそらく、Draw()WM_PAINTのときに行う処理を実装する。 でも、DirectXはメッセージループでなく、メインループで描画するもののはず。 WndProc内の一部としてDirectXを実装してしまっていいのか不明。

GDI/GDI+/DirectX9やメッセージループのことも良くわかっていないので、Draw()に統一できるのかどうか不透明。検討しきれない。