椿の日記

たぶんプログラムの話をします

MinGWからDirect3D11を呼び出す

色々知らないことが多くて大変でした… 忘れないようにメモを残します。

MinGWのバージョン

$ g++ --version
g++.exe (GCC) 3.4.5 (mingw-vista special r3)

d3d11.hのコンパイルを通す

まずコンパイル通すためだけにテスト用のソースを作ります。

// compile.cpp
#include "d3d11.h"

これをコンパイルが通るようになるまでヘッダの前方にプロプロセッサを定義して修正していきます。
次のようなヘッダを書き、先にインクルードすることで解決できるようです。

// for_mingw.h
#define __in
#define __out
#define __inout
#define __in_bcount(x)
#define __out_bcount(x)
#define __in_ecount(x)
#define __out_ecount(x)
#define __in_ecount_opt(x)
#define __out_ecount_opt(x)
#define __in_bcount_opt(x)
#define __out_bcount_opt(x)
#define __in_opt
#define __inout_opt
#define __out_opt
#define __out_ecount_part_opt(x,y)
#define __deref_out
#define __deref_out_opt
#define __RPC__deref_out

#include "stdint.h"

typedef uint8_t UINT8;

.libから.defを抽出して.aを作成する

iconvでやったことの繰り返しになります。

$ reimp -c -d "C:\usr\bin\Microsoft DirectX SDK (June 2010)\Lib\x86\d3d11.lib"
$ dlltool -d d3d11.def -l libd3d11.a

これで、.aが作れました。

Cのソースを書いてビルド

// main.cpp
#include "for_mingw.h"
#include "d3d11.h"

#define ARRAYSIZE(x) (sizeof(x)/sizeof(x[0]))

const GUID IID_ID3D11Texture2D = { 0x6f15aaf2,0xd208,0x4e89, { 0x9a,0xb4,0x48,0x95,0x35,0xd3,0x4f,0x9c } };

////////////////////////////////////////////////////////////////////////////////

struct MNContext
{
    ID3D11Device* pDevice;
    IDXGISwapChain* pSwapChain;
    ID3D11DeviceContext* pImmediateContext;
    ID3D11RenderTargetView* pRenderTargetView;
};

extern "C" HRESULT MNCreateContext( HWND hwnd, MNContext** ppOut )
{
    MNContext* pContext = new MNContext;

    HRESULT hr;

    uint32_t width = 640;
    uint32_t height = 480;

    // デバイスとスワップチェインを構築
    UINT createDeviceFlags = 0;

    D3D_DRIVER_TYPE driverTypes[] =
    {
        D3D_DRIVER_TYPE_HARDWARE,
        D3D_DRIVER_TYPE_WARP,
        D3D_DRIVER_TYPE_REFERENCE,
    };
    UINT numDriverTypes = ARRAYSIZE( driverTypes );

    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
    };
    UINT numFeatureLevels = ARRAYSIZE( featureLevels );

    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory( &sd, sizeof( sd ) );
    sd.BufferCount = 1;
    sd.BufferDesc.Width = width;
    sd.BufferDesc.Height = height;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = hwnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;

    D3D_FEATURE_LEVEL featureLevel;

    for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
    {
        hr = D3D11CreateDeviceAndSwapChain(
            NULL,
            driverTypes[driverTypeIndex],
            NULL,
            createDeviceFlags,
            featureLevels,
            numFeatureLevels,
            D3D11_SDK_VERSION,
            &sd,
            &pContext->pSwapChain,
            &pContext->pDevice,
            &featureLevel,
            &pContext->pImmediateContext
            );
        if( SUCCEEDED( hr ) )
            break;
    }
    if( FAILED( hr ) )
        return hr;

    // レンダーターゲットビューを構築
    ID3D11Texture2D* pBackBuffer = NULL;
    hr = pContext->pSwapChain->GetBuffer( 0, IID_ID3D11Texture2D, ( LPVOID* )&pBackBuffer );
    if( FAILED( hr ) )
    {
        return hr;
    }

    hr = pContext->pDevice->CreateRenderTargetView( pBackBuffer, NULL, &pContext->pRenderTargetView );
    pBackBuffer->Release();
    if( FAILED( hr ) )
    {
        return hr;
    }

    pContext->pImmediateContext->OMSetRenderTargets( 1, &pContext->pRenderTargetView, NULL );

    // ビューポートを設定
    D3D11_VIEWPORT vp;
    vp.Width = (FLOAT)width;
    vp.Height = (FLOAT)height;
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;
    pContext->pImmediateContext->RSSetViewports( 1, &vp );

    *ppOut = pContext;
    return S_OK; 
}

extern "C" void MNDeleteContext( MNContext* pContext )
{
    pContext->pImmediateContext->ClearState();

    pContext->pRenderTargetView->Release();
    pContext->pImmediateContext->Release();
    pContext->pDevice->Release();
    pContext->pSwapChain->Release();
    delete pContext;
}

extern "C" void MNPresent( MNContext* pContext )
{
    float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f };
    pContext->pImmediateContext->ClearRenderTargetView( pContext->pRenderTargetView, ClearColor );
    pContext->pSwapChain->Present( 0, 0 );
}

////////////////////////////////////////////////////////////////////////////////

char g_myClassName[] = "My Class Name";

LRESULT CALLBACK MyWndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch( message )
    {
    case WM_DESTROY:
        PostQuitMessage( 0 );
        break;
    }

    return DefWindowProc( hwnd, message, wParam, lParam );
}

void RegisterMyClass()
{
    WNDCLASS wc;
    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc        = MyWndProc;
    wc.cbClsExtra        = 0;
    wc.cbWndExtra        = 0;
    wc.hInstance        = GetModuleHandle( NULL );
    wc.hIcon            = LoadIcon( NULL, IDI_APPLICATION );
    wc.hCursor            = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground    = (HBRUSH)GetStockObject( BLACK_BRUSH );
    wc.lpszMenuName        = NULL;
    wc.lpszClassName    = g_myClassName;
    RegisterClass( &wc );
}

HWND CreateMyWindow()
{
    return CreateWindowEx(
        0,
        g_myClassName,
        "test window",
        WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_MINIMIZEBOX,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        GetModuleHandle( NULL ),
        NULL );
}

void Update( MNContext* pContext )
{
    MNPresent( pContext );
    Sleep( 1 );
}

void ExecuteMessageLoop( MNContext* pContext )
{
    MSG msg;

    for(;;)
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            if( msg.message == WM_QUIT )
            {
                break;
            }
            else
            {
                TranslateMessage( &msg );
                DispatchMessage( &msg );
            }
        }
        else
        {
            Update( pContext );
        }
    }
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow )
{
    RegisterMyClass();

    HWND hwnd = CreateMyWindow();
    MNContext* pContext = 0;

    HRESULT hr = MNCreateContext( hwnd, &pContext );
    if(FAILED(hr))
    {
        return -1;
    }

    ShowWindow( hwnd, SW_NORMAL );
    UpdateWindow( hwnd );
    ExecuteMessageLoop( pContext );

    MNDeleteContext( pContext );

    return 0;
}

そしてビルドして実行。

$ g++ main.cpp -L. -ld3d11 -I"C:\usr\bin\Microsoft DirectX SDK (June 2010)\Include" -mwindows
$ a.exe

D3D11CreateDeviceAndSwapChain@48が見つからない、というエラーが出ます。

f:id:tbk:20110115171518p:image

.defの中身と.aの作り直し

この@48ってDLLの序数(GetProcAddressとかで取得するときのインデックス)のこと?と思って.defの中身を見てみたところ、なんか微妙に違う。

LIBRARY "d3d11.dll"
EXPORTS
D3DKMTCloseAdapter@@YGJPAU_D3DKMT_CLOSEADAPTER@@@Z
D3DKMTDestroyAllocation@@YGJPAU_D3DKMT_DESTROYALLOCATION@@@Z
D3DKMTDestroyContext@@YGJPAU_D3DKMT_DESTROYCONTEXT@@@Z
D3DKMTDestroyDevice@@YGJPAU_D3DKMT_DESTROYDEVICE@@@Z
D3DKMTDestroySynchronizationObject@@YGJPAU_D3DKMT_DESTROYSYNCHRONIZATIONOBJECT@@@Z
D3DKMTQueryAdapterInfo@@YGJPAU_D3DKMT_QUERYADAPTERINFO@@@Z
D3DKMTSetDisplayPrivateDriverFormat@@YGJPAU_D3DKMT_SETDISPLAYPRIVATEDRIVERFORMAT@@@Z
D3DKMTSignalSynchronizationObject@@YGJPAU_D3DKMT_SIGNALSYNCHRONIZATIONOBJECT@@@Z
D3DKMTUnlock@@YGJPAU_D3DKMT_UNLOCK@@@Z
D3DKMTWaitForSynchronizationObject@@YGJPAU_D3DKMT_WAITFORSYNCHRONIZATIONOBJECT@@@Z
OpenAdapter10@@YGJPAUD3D10DDIARG_OPENADAPTER@@@Z
OpenAdapter10_2@@YGJPAUD3D10DDIARG_OPENADAPTER@@@Z
D3D11CoreCreateDevice@40
D3D11CoreCreateLayeredDevice@20
D3D11CoreGetLayeredDeviceSize@8
D3D11CoreRegisterLayers@8
D3D11CreateDevice@40
D3D11CreateDeviceAndSwapChain@48
D3DKMTCreateAllocation@4
D3DKMTCreateContext@4
D3DKMTCreateDevice@4
(以下略)

この@4だの@48だのというのは__stdcallの呼び出し規約で勝手に付くサフィックスなんだそうです。これはdlltoolで.aを作るときに-kを付けて@XXを無視できる.aを作れば解決できるようです。

$ dlltool -d d3d11.def -l libd3d11.a -k
$ g++ main.cpp -L. -ld3d11 -I"C:\usr\bin\Microsoft DirectX SDK (June 2010)\Include" -mwindows
$ a.exe

これでようやく起動できました。
英語ですが、ここがとてもよいヒントになりました。