Platform SDK: DirectX

DirectDraw 協調レベルと FPU 精度

[Visual Basic]

 :  ここでは、C++ でのアプリケーション開発について説明する。DirectX for Visual Basic では、浮動小数点単位 (FPU: floating-point unit) 精度の変更をサポートしていない。

[C++]

Direct3D では、常に単精度浮動小数点計算を使用して、3D シーンのレンダリングのパフォーマンスを向上させる。デフォルトでは、IDirect3DDevice7 インターフェイスをサポートするレンダリング デバイスを作成するとき、Direct3D は FPU を単精度、直近値丸めモードで例外無効に設定する。この時点から、そのデバイスが解放されるまでは FPU のステートは変更されないことが前提となり、デバイスが解放されると FPU は倍精度モードにリセットされる。

 :  このようなシステム動作は、従来の IDirect3DDevice3 インターフェイスのものとは異なる。IDirect3DDevice3 を公開するデバイスは、前の IDirectDraw4::SetCooperative メソッドの呼び出しで DirectDraw 協調レベルを設定したときに DDSCL_FPUSETUP フラグが使用されていない場合に限り、各レンダリング パスの FPU モードをチェックする。

倍精度の精度が必要なアプリケーションでは、IDirectDraw7::SetCooperativeLevel メソッドを呼び出して DirectDraw 協調レベルを設定するときに DDSCL_FPUPRESERVE フラグを含めることができる。DDSCL_FPUPRESERVE フラグを使用すると、単精度モードが使用されるが、精度モードを変更する前に FPU のステートが保存され、制御をアプリケーションに返すときに元のモードが復元される (DDSCL_FPUSETUP フラグを使用して再度メソッドを呼び出すと、Direct3D はデフォルトの動作に戻る)。このチェック、保存、および復元処理には明らかに時間がかかり、パフォーマンスに大きく影響することもある。このフラグは、アプリケーションで倍精度を使用する必要があり、アプリケーションでの必要に応じて FPU モードを手動で設定および復元するコードを含めない場合にのみ使用するようにする。

浮動小数点精度はスレッド固有であるため、マルチ スレッド アプリケーションを開発するときは、FPU の精度ステートをチェックして、スレッドごとに設定とリセットが適切に行われているかどうかを確認する必要がある。

重要  一部のダイナミック リンク ライブラリ (DLL) を実行時にロードすると、FPU が倍精度モードにリセットされることがある。Visual C++ などのコンパイラは、C/C++ ランタイム コンポーネントを初期化する _DllMainCrtStartup にデフォルトの DLL エントリ ポイントを設定する。この関数も、FPU 精度モードを倍精度に設定する。アプリケーションが DDSCL_FPUSETUP 協調レベルを設定した後て DLL をロードすると、Direct3D は FPU がリセットされたことを検出できないため、パフォーマンスに影響する。

実行時に DLL をロードする場合は、LoadLibrary Win32 関数が戻った直後と Direct3D 関数を呼び出す前に、FPU 精度をチェックしてリセットする必要がある。精度モードをリセットするには、明示的にリセットするか、DDSCL_FPUSETUP フラグを設定して IDirectDraw7::SetCooperativeLevel メソッドを再度呼び出す。SetCooperativeLevel を使用して FPU 精度モードを設定すると、DirectDraw サーフェイスが失われる。

/ENTRY: リンカ スイッチを使用すれば、コンパイルする DLL のエントリ ポイントを明示的に設定できる。ただし、この場合、C/C++ ランタイムは自動的には初期化されない。

次に示すのは、インライン アセンブリ言語を使用して、FPU 精度の設定をチェックおよび設定するコンソール アプリケーション用のサンプル ソース ファイルである。

#include <windows.h>
#include <math.h>
 
// この関数は、
// 浮動小数点制御語が、単精度/直近値丸め/例外無効に設定されているかどうかを
// 評価する。設定されていない場合、
// 関数は制御語を変更して条件を設定し、TRUE を返す。
// 古い制御語値は、pwOldCW が
// ポイントするパスバック位置座標に配置する。
BOOL MungeFPCW( WORD *pwOldCW )
{
    BOOL ret = FALSE;
    WORD wTemp, wSave;
 
    __asm fstcw wSave
    if (wSave & 0x300 ||            // 単精度モードではない。
        0x3f != (wSave & 0x3f) ||   // 例外有効。
        wSave & 0xC00)              // 直近値丸めモードではない。
    {
        __asm
        {
            mov ax, wSave
            and ax, not 300h    ;; 単精度モード。
            or  ax, 3fh         ;; すべての例外無効。
            and ax, not 0xC00   ;; 直近値丸めモード
            mov wTemp, ax
            fldcw   wTemp
        }
        ret = TRUE;
    }
    *pwOldCW = wSave;
    return ret;
}
 
void RestoreFPCW(WORD wSave)
{
    __asm fldcw wSave
}
 
void __cdecl main()
{
    WORD wOldCW;
    BOOL bChangedFPCW = MungeFPCW( &wOldCW );
    // MungeFPCW の設定に従って制御語を処理する。
    if ( bChangedFPCW )
        RestoreFPCW( wOldCW );
}