WPFとDirect3D11の連携 --- 三角形描画

下記記事にてWPFとDirect3D11を連携してサーフェスを表示しました。
WPFとDirect3D11の連携 --- 導入 - 何でもプログラミング

今回は単純な三角形を描画してWPFで表示してみたいと思います。

Shader作成(csoから)

csoデータが存在する場合は、下記関数のみで作成できます。

VertexShaderではInputLayoutも併せて作成しています。

#define AssertHR(expr) { HRESULT hr = expr; if (FAILED(hr)) throw gcnew ComponentModel::Win32Exception(hr, #expr); }

std::tuple<CComPtr<ID3D11VertexShader>, CComPtr<ID3D11InputLayout>> CreateVertexShader(
    ID3D11Device* device, const std::vector<byte>& cso, const std::vector<D3D11_INPUT_ELEMENT_DESC>& inputDesc)
{
    CComPtr<ID3D11VertexShader> shader;        
    AssertHR(device->CreateVertexShader(cso.data(), cso.size(), NULL, &shader.p));

    CComPtr<ID3D11InputLayout> inputLayout;
    AssertHR(device->CreateInputLayout(inputDesc.data(), inputDesc.size(), cso.data(), cso.size(), &inputLayout.p));

    return { shader, inputLayout };
}
CComPtr<ID3D11PixelShader> CreatePixelShader(ID3D11Device* device, const std::vector<byte>& cso)
{
    CComPtr<ID3D11PixelShader> shader;
    AssertHR(device->CreatePixelShader(cso.data(), cso.size(), NULL, &shader.p));
    return shader;
}


Shader作成(コードから)

実行時にコンパイルする場合は、下記関数を利用します。

ターゲット(vs_4_0_level_9_1、ps_4_0_level_9_1)は適宜変更してください。

Compile関数は下記記事を参照してください。
WPFとDirect3D9Exの連携 --- 三角形描画 - 何でもプログラミング

std::tuple<CComPtr<ID3D11VertexShader>, CComPtr<ID3D11InputLayout>> CreateVertexShader(
    ID3D11Device* device, std::string code, const std::vector<D3D11_INPUT_ELEMENT_DESC>& inputDesc)
{
    auto blob = Compile(code, "vs_4_0_level_9_1");
    auto p = (byte*)blob->GetBufferPointer();
    std::vector<byte> cso(p, p + blob->GetBufferSize());
    return CreateVertexShader(device, cso, inputDesc);
}
CComPtr<ID3D11PixelShader> CreatePixelShader(ID3D11Device* device, std::string code)
{
    auto blob = Compile(code, "ps_4_0_level_9_1");
    auto p = (byte*)blob->GetBufferPointer();
    std::vector<byte> cso(p, p + blob->GetBufferSize());
    return CreatePixelShader(device, cso);
}


VertexBuffer作成

template <class T>
CComPtr<ID3D11Buffer> CreateVertexBuffer(ID3D11Device* device, const std::vector<T>& vertices)
{
    D3D11_BUFFER_DESC desc;
    ZeroMemory(&desc, sizeof(desc));
    desc.Usage = D3D11_USAGE_DEFAULT;
    desc.ByteWidth = vertices.size() * sizeof(T);
    desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    desc.CPUAccessFlags = 0;

    D3D11_SUBRESOURCE_DATA data;
    ZeroMemory(&data, sizeof(data));
    data.pSysMem = vertices.data();

    CComPtr<ID3D11Buffer> buffer;
    AssertHR(device->CreateBuffer(&desc, &data, &buffer));
  
    return buffer;
}


Renderer作成

CliComPtr、D3D9Ex::CreateDeviceは下記記事を参照してください。
WPFとDirect3D9Exの連携 --- 導入 - 何でもプログラミング

CreateSharedRenderTarget、CreateDevice、OpenRenderTarget、Tie2は下記記事を参照してください。
WPFとDirect3D11の連携 --- 導入 - 何でもプログラミング

struct Vertex
{
    float X, Y, Z;
};
public ref class Renderer
{
public:
    property IntPtr Surface { IntPtr get() { return IntPtr(_surface9); } }
    Renderer(int width, int height)
    {
        _device9 = D3D9Ex::CreateDevice();
        HANDLE handle;
        Tie2(_surface9, handle, CreateSharedRenderTarget(_device9, width, height));

        Tie2(_device, _context, CreateDevice());
        _renderTarget = OpenRenderTarget(_device, handle);
            
        // Shader作成
        std::string vertexCode =
            "float4 main(float4 position : POSITION) : SV_POSITION {  \n"
            "    return position;                                     \n"
            "}                                                        \n";
        std::vector<D3D11_INPUT_ELEMENT_DESC> inputDesc =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }
        };
        Tie2(_vertexShader, _inputLayout, CreateVertexShader(_device, vertexCode, inputDesc));

        std::string pixelCode =
            "float4 main() : SV_TARGET {                 \n"
            "    return float4(1.0f, 0.0f, 0.0f, 1.0f);  \n"
            "}                                           \n";
        _pixelShader = CreatePixelShader(_device, pixelCode);

        // VertexBuffer作成
        std::vector<Vertex> vertices =
        {
            {  0.0f,  1.0f, 0.5f },
            {  1.0f, -1.0f, 0.5f },
            { -1.0f, -1.0f, 0.5f },
        };
        _vertexBuffer = CreateVertexBuffer(_device, vertices);

        // 描画準備
        ID3D11RenderTargetView* renderTargets[1] = { _renderTarget };
        _context->OMSetRenderTargets(1, renderTargets, NULL);

        _context->VSSetShader(_vertexShader, NULL, 0);
        _context->PSSetShader(_pixelShader, NULL, 0);
        _context->IASetInputLayout(_inputLayout);

        UINT stride = sizeof(Vertex);
        UINT offset = 0;
        ID3D11Buffer* vertexBuffers[1] = { _vertexBuffer };
        _context->IASetVertexBuffers(0, 1, vertexBuffers, &stride, &offset);

        D3D11_VIEWPORT viewport;
        viewport.Width = (float)width;
        viewport.Height = (float)height;
        viewport.MinDepth = 0.0f;
        viewport.MaxDepth = 1.0f;
        viewport.TopLeftX = 0.0f;
        viewport.TopLeftY = 0.0f;
        _context->RSSetViewports(1, &viewport);

        // 描画
        float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
        _context->ClearRenderTargetView(_renderTarget, clearColor);

        _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        _context->Draw(3, 0);

        _context->Flush();
    };
private:
    CliComPtr<IDirect3DDevice9Ex> _device9;
    CliComPtr<IDirect3DSurface9> _surface9;
    CliComPtr<ID3D11Device> _device;
    CliComPtr<ID3D11DeviceContext> _context;
    CliComPtr<ID3D11RenderTargetView> _renderTarget;
    CliComPtr<ID3D11VertexShader> _vertexShader;
    CliComPtr<ID3D11InputLayout> _inputLayout;
    CliComPtr<ID3D11PixelShader> _pixelShader;
    CliComPtr<ID3D11Buffer> _vertexBuffer;
};


WPF

下記記事と同じものを利用します。
WPFとDirect3D9Exの連携 --- 導入 - 何でもプログラミング

下記のようなウィンドウが表示されます。
f:id:any-programming:20170502173131p:plain





WPFとDirect3D11の連携 --- 導入

下記記事にてWPFとDirect3D9Exを連携する方法を記述しました。
WPFとDirect3D9Exの連携 --- 導入 - 何でもプログラミング

最近ではDirect3D11の利用がメジャーになっていますので、今回はWPFとDirect3D11を連携させてみたいと思います。

WPF自体はDirect3D9ですので、リソースを共有する仕組みでDirect3D11と連携します。

本記事で利用しているCliComPtr、AssertHR、D3D9Ex::CreateDeviceは上記記事を参照してください。

Device作成

DeviceとDeviceContextを作成しています。

createDeviceFlagやpFeatureLevelsは適宜カスタマイズしてください。(下記ではFeatureLevelは11、10.1、10、9.3、9.2、9.1から選択されます。)

#include <d3d11.h>
#pragma comment(lib, "d3d11.lib")

std::tuple<CComPtr<ID3D11Device>, CComPtr<ID3D11DeviceContext>> CreateDevice()
{
#ifdef _DEBUG
    DWORD createDeviceFlag = D3D11_CREATE_DEVICE_DEBUG;
#else
    DWORD createDeviceFlag = 0;
#endif

    CComPtr<ID3D11Device> device;
    CComPtr<ID3D11DeviceContext> context;
    D3D_FEATURE_LEVEL createdFeatureLevel;
    AssertHR(D3D11CreateDevice(
        NULL,
        D3D_DRIVER_TYPE_HARDWARE,
        NULL,
        createDeviceFlag,
        NULL,
        0,
        D3D11_SDK_VERSION,
        &device.p,
        &createdFeatureLevel,
        &context.p));

    return { device, context };
}


共有RenderTarget作成(Direct3D9Ex)

Direct3D11で利用できるDirect3D9ExのRenderTargetを作成します。

CreateRenderTargetの最後の引数からHANDLEを取得しているとこが通常との違いです。

std::tuple<CComPtr<IDirect3DSurface9>, HANDLE> CreateSharedRenderTarget(IDirect3DDevice9Ex* device, int width, int height)
{
    CComPtr<IDirect3DSurface9> surface;
    HANDLE handle = nullptr;
    AssertHR(device->CreateRenderTarget(
        width,
        height,
        D3DFMT_A8R8G8B8,
        D3DMULTISAMPLE_NONE,
        0,
        FALSE,
        &surface.p,
        &handle));
       
    return { surface, handle };
}


共有RenderTargetを開く

Direct3D9Exで作成したRenderTargetをDirect3D11側で開きます。

CComPtr<ID3D11RenderTargetView> OpenRenderTarget(ID3D11Device* device, HANDLE handle)
{        
    CComPtr<ID3D11Resource> resource;
    AssertHR(device->OpenSharedResource(handle, __uuidof(ID3D11Resource), (void**)&resource.p));

    CComPtr<ID3D11RenderTargetView> renderTarget;
    AssertHR(device->CreateRenderTargetView(resource, NULL, &renderTarget.p));

    return renderTarget;
}


Renderer作成

赤い背景を描画するだけの内容となっています。

ref classとstd::tieを上手く連携させられなかったため、Tie2マクロを定義しています。

共有リソースを利用している場合は描画の最後にID3D11DeviceContext::Flushを呼び出す必要があります。

#define Tie2(x1, x2, expr) { auto t = expr; x1 = std::get<0>(t); x2 = std::get<1>(t); }

public ref class Renderer
{
public:
    property IntPtr Surface { IntPtr get() { return IntPtr(_surface9); } }
    Renderer(int width, int height)
    {
        _device9 = D3D9Ex::CreateDevice();

        HANDLE handle;
        Tie2(_surface9, handle, D3D9Ex::CreateSharedRenderTarget(_device9, width, height));

        Tie2(_device, _context, CreateDevice());

        _renderTarget = OpenRenderTarget(_device, handle);

        ID3D11RenderTargetView* renderTargets[1] = { _renderTarget };
        _context->OMSetRenderTargets(1, renderTargets, NULL);

        float clearColor[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
        _context->ClearRenderTargetView(_renderTarget, clearColor);

        _context->Flush();
    };
private:
    CliComPtr<IDirect3DDevice9Ex> _device9;
    CliComPtr<IDirect3DSurface9> _surface9;
    CliComPtr<ID3D11Device> _device;
    CliComPtr<ID3D11DeviceContext> _context;
    CliComPtr<ID3D11RenderTargetView> _renderTarget;
};


WPF

下記記事と同じコードを利用できます。
WPFとDirect3D9Exの連携 --- 導入 - 何でもプログラミング

下記のようなウィンドウが表示されます。
f:id:any-programming:20170502152556p:plain





WPFとDirect3D9Exの連携 --- テクスチャ貼り付け

下記記事にて三角形を描画するところまで実装しました。
WPFとDirect3D9Exの連携 --- 三角形描画 - 何でもプログラミング

今回はさらにテクスチャを貼り付けを実装してみたいと思います。

本記事で利用している、CreateVertexShader、CreatePixelShader、CreateVertexBufferは上記記事を、
CliComPtr、AssertHR、CreateDevice、CreateRenderTargetは下記記事を参照してください。
WPFとDirect3D9Exの連携 --- 導入 - 何でもプログラミング

Texture作成

X8R8G8B8フォーマットで作成しています。(byte列ではBGRA)

CPU側から書き込むため、D3DPOOL_SYSTEMMEMを指定しています。

CComPtr<IDirect3DTexture9> CreateTexture(IDirect3DDevice9Ex* device, int width, int height, const byte* pixels)
{
    CComPtr<IDirect3DTexture9> texture;
    AssertHR(device->CreateTexture(
        width,
        height,
        1,
        0,
        D3DFMT_X8R8G8B8,
        D3DPOOL_SYSTEMMEM,
        &texture.p,
        NULL));

    D3DLOCKED_RECT lockedRect;
    AssertHR(texture->LockRect(0, &lockedRect, NULL, D3DLOCK_DISCARD));
    for (int y = 0; y < height; ++y)
        memcpy((byte*)lockedRect.pBits + y * lockedRect.Pitch, pixels + y * 4 * width, 4 * width);
    AssertHR(texture->UnlockRect(0));

    return texture;
}


Renderer作成

入力データからテクスチャを作成し、ターゲット全画面にレンダリングしています。

struct Vertex
{
    static const DWORD FVF = D3DFVF_XYZ | D3DFVF_TEX1;
    FLOAT X, Y, Z;
    FLOAT U, V;
};
public ref class Renderer
{
public:
    property IntPtr Surface { IntPtr get() { return IntPtr(_surface); } }
    Renderer(int width, int height, array<byte>^ pixels)
    {
        _device = CreateDevice();
        _surface = CreateRenderTarget(_device, width, height);

        // Shader作成
        std::string vertexCode =
            "struct VS_OUTPUT {                     \n"
            "    float4 Position : POSITION;        \n"
            "    float2 Texcoord : TEXCOORD0;       \n"
            "};                                     \n"
            "VS_OUTPUT main(                        \n"
            "    float4 position : POSITION,        \n"
            "    float2 texcoord : TEXCOORD0) {     \n"
            "    VS_OUTPUT output = (VS_OUTPUT)0;   \n"
            "    output.Position = position;        \n"
            "    output.Texcoord = texcoord;        \n"
            "    return output;                     \n"
            "}                                      \n";
        _vertexShader = CreateVertexShader(_device, vertexCode);

        std::string pixelCode =
            "Texture2D texture0 : register(t0);                      \n"
            "SamplerState texture0Sampler {                          \n"
            "   Filter = MIN_MAG_MIP_POINT;                          \n"
            "   AddressU = Clamp;                                    \n"
            "   AddressV = Clamp;                                    \n"
            "};                                                      \n"
            "float4 main(float2 texcoord : TEXCOORD0) : COLOR {      \n"
            "   return texture0.Sample(texture0Sampler, texcoord);   \n"
            "}                                                       \n";
        _pixelShader - CreatePixelShader(_device, pixelCode);

        // VertexBuffer作成
        std::vector<Vertex> vertices = 
        {
            { -1.0f,  1.0f, 0.5f, 0.0f, 0.0f },
            {  1.0f,  1.0f, 0.5f, 1.0f, 0.0f },
            {  1.0f, -1.0f, 0.5f, 1.0f, 1.0f },
            { -1.0f, -1.0f, 0.5f, 0.0f, 1.0f },
        };
        _vertexBuffer = CreateVertexBuffer(_device, vertices);

        // Texture作成
        pin_ptr<byte> pixelsPtr = &pixels[0];
        _texture = CreateTexture(_device, width, height, pixelsPtr);

        // 描画準備
        AssertHR(_device->SetRenderTarget(0, _surface));
        AssertHR(_device->SetFVF(Vertex::FVF));
        AssertHR(_device->SetStreamSource(0, _vertexBuffer, 0, sizeof(Vertex)));
        AssertHR(_device->SetVertexShader(_vertexShader));
        AssertHR(_device->SetPixelShader(_pixelShader));
        AssertHR(_device->SetTexture(0, _texture));

        // 描画
        AssertHR(_device->BeginScene());            
        AssertHR(_device->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2));
        AssertHR(_device->EndScene());
    }
private:
    CliComPtr<IDirect3DDevice9Ex> _device;
    CliComPtr<IDirect3DSurface9> _surface;
    CliComPtr<IDirect3DVertexShader9> _vertexShader;
    CliComPtr<IDirect3DPixelShader9> _pixelShader;
    CliComPtr<IDirect3DVertexBuffer9> _vertexBuffer;
    CliComPtr<IDirect3DTexture9> _texture;
};


WPF

XamlとCreateD3DImageは下記記事のものを利用します。
WPFとDirect3D9Exの連携 --- 導入 - 何でもプログラミング

C#側に、画像ファイルを読み込んでRendererを作成する部分を追加します。

var src = new BitmapImage(new Uri("Parrots.bmp", UriKind.Relative));
int width = src.PixelWidth;
int height = src.PixelHeight;
byte[] pixels = new byte[4 * width * height];
src.CopyPixels(pixels, 4 * width, 0);

var renderer = new Renderer(width, height, pixels);

image.Source = CreateD3DImage(width, height, renderer.Surface);

f:id:any-programming:20170502180307p:plain





WPFとDirect3D9Exの連携 --- 三角形描画

下記記事にてIDirect3DSurface9をWPFで表示しました。
WPFとDirect3D9Exの連携 --- 導入 - 何でもプログラミング

今回は単純な三角形を描画してWPFで表示してみたいと思います。

本記事で利用しているAssertHR、CliComPtr、CreateDevice、CreateRenderTargetに関しましては、上記記事を参照してください.

Shader作成(csoから)

csoデータが存在する場合は、下記関数のみで作成できます。

CComPtr<IDirect3DVertexShader9> CreateVertexShader(IDirect3DDevice9Ex* device, const DWORD* cso)
{
    CComPtr<IDirect3DVertexShader9> shader;
    AssertHR(device->CreateVertexShader(cso, &shader.p));
    return shader;
}
CComPtr<IDirect3DPixelShader9> CreatePixelShader(IDirect3DDevice9Ex* device, const DWORD* cso)
{
    CComPtr<IDirect3DPixelShader9> shader;
    AssertHR(device->CreatePixelShader(cso, &shader.p));
    return shader;
}


Shader作成(コードから)

実行時にコンパイルする場合は、下記関数を利用します。

Direct3D9Exを利用しているため、vs_3_0、ps_3_0より後のバージョンは利用できません。

コンパイルオプション(flags1)は適宜変更してください。

CComPtr<IDirect3DVertexShader9> CreateVertexShader(IDirect3DDevice9Ex* device, std::string code)
{
    return CreateVertexShader(device, (const DWORD*)Compile(code, "vs_3_0")->GetBufferPointer());
}
CComPtr<IDirect3DPixelShader9> CreatePixelShader(IDirect3DDevice9Ex* device, std::string code)
{
    return CreatePixelShader(device, (const DWORD*)Compile(code, "ps_3_0")->GetBufferPointer());
}
#include <d3dcompiler.h>
#pragma comment(lib, "d3dcompiler.lib")

CComPtr<ID3DBlob> Compile(std::string code, std::string target)
{
#ifdef _DEBUG
    UINT flags1 = D3DCOMPILE_DEBUG | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS;
#else
    UINT flags1 = D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS;
#endif
    CComPtr<ID3DBlob> compiled, errorMessage;
    D3DCompile(
        code.c_str(), 
        code.size(), 
        NULL, 
        NULL, 
        D3D_COMPILE_STANDARD_FILE_INCLUDE, 
        "main", 
        target.c_str(), 
        flags1, 
        0, 
        &compiled.p, 
        &errorMessage.p);
    if (errorMessage)
        throw gcnew Exception(gcnew String((const char*)errorMessage->GetBufferPointer()));
    
    return compiled;
}


VertexBuffer作成

対象のVertexクラスには、FVF staticメンバが定義されていることを想定しています。

template <class T>
CComPtr<IDirect3DVertexBuffer9> CreateVertexBuffer(IDirect3DDevice9Ex* device, const std::vector<T>& vertices)
{
    CComPtr<IDirect3DVertexBuffer9> buffer;
    AssertHR(device->CreateVertexBuffer(
        vertices.size() * sizeof(T),
        0,
        T::FVF,
        D3DPOOL_DEFAULT,
        &buffer.p,
        NULL));
    
    void* p;
    AssertHR(buffer->Lock(0, 0, &p, 0));
    memcpy(p, vertices.data(), vertices.size() * sizeof(T));
    AssertHR(buffer->Unlock());

    return buffer;
}


Renderer作成

Shader、VertexBufferを作成して、三角形を描画しています。

struct Vertex
{
    static const DWORD FVF = D3DFVF_XYZ;
    FLOAT X, Y, Z;
};
public ref class Renderer
{
public:
    property IntPtr Surface { IntPtr get() { return IntPtr(_surface); } }
    Renderer(int width, int height)
    {
        _device = CreateDevice();
        _surface = CreateRenderTarget(_device, width, height);

        // Shader作成
        std::string vertexCode =
            "float4 main(float4 position : POSITION) : POSITION {  \n"
            "    return position;                                  \n"
            "}                                                     \n";
        _vertexShader = CreateVertexShader(_device, vertexCode);

        std::string pixelCode =
            "float4 main() : COLOR {                     \n"
            "    return float4(1.0f, 0.0f, 0.0f, 1.0f);  \n"
            "}                                           \n";
        _pixelShader = CreatePixelShader(_device, pixelCode);

        // VertexBuffer作成
        std::vector<Vertex> vertices = 
        {
            {  0.0f,  1.0f, 0.5f },
            {  1.0f, -1.0f, 0.5f },
            { -1.0f, -1.0f, 0.5f },
        };
        _vertexBuffer = CreateVertexBuffer(_device, vertices);

        // 描画準備
        AssertHR(_device->SetRenderTarget(0, _surface));
        AssertHR(_device->SetFVF(Vertex::FVF));
        AssertHR(_device->SetStreamSource(0, _vertexBuffer, 0, sizeof(Vertex)));
        AssertHR(_device->SetVertexShader(_vertexShader));
        AssertHR(_device->SetPixelShader(_pixelShader));

        // 描画
        AssertHR(_device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0));
        AssertHR(_device->BeginScene());            
        AssertHR(_device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1));            
        AssertHR(_device->EndScene());
    }
private:
    CliComPtr<IDirect3DDevice9Ex> _device;
    CliComPtr<IDirect3DSurface9> _surface;
    CliComPtr<IDirect3DVertexShader9> _vertexShader;
    CliComPtr<IDirect3DPixelShader9> _pixelShader;
    CliComPtr<IDirect3DVertexBuffer9> _vertexBuffer;
};


WPF

WPF側は下記記事と同じものを利用します。
WPFとDirect3D9Exの連携 --- 導入 - 何でもプログラミング

実行すると下記のようなウィンドウが表示されます。
f:id:any-programming:20170502173131p:plain





WPFとDirect3D9Exの連携 --- 導入

WPFDirect3Dレンダリング結果を貼り付けたい場合、D3DImageクラスが利用できます。

CPU経由でBitmapSourceを作成するのに比べ、D3DImageはGPU内で完結するため、より効率的です。

今回はD3DImageを利用するところまでを実装したいと思います。

C++/CLI用ComPtr

C++/CLIのクラスではC++のクラスをメンバーに持てず、生のポインタを自分で管理する必要があります。

そこでComPtrをC++/CLIクラスのメンバで管理できるクラスを作成します。

このクラスをスタックで利用(gcnewなし)することにより、スコープから抜けた時や、親クラスのDisposeが呼ばれたときに破棄されるようになります。

最低限のものですので、コピーコンストラクタなどは適宜実装してください。

template <class T>
ref class CliComPtr
{
public:
    !CliComPtr() { Release(); }
    ~CliComPtr() { CliComPtr::!CliComPtr(); }
    T* operator->() { return _p; }
    void operator = (T* p)
    {
        Release();
        _p = p;
        _p->AddRef();
    }
private:
    T* _p;
    void Release()
    {
        if (_p != nullptr)
        {
            _p->Release();
            _p = nullptr;
        }
    }
};


Device作成

WPFはDirect3D9しか利用できない為、IDirect3DDevice9Exを作成しています。

バックバッファは利用しない為、サイズは適当です。

HResult確認用に、AssertHRマクロを定義してあります。

// CComPtr用
#include <atlcomcli.h>
#pragma comment(lib, "oleaut32.lib")

// D3D9Ex用
#include <d3d9.h>
#pragma comment(lib, "d3d9.lib")

using namespace System;

#define AssertHR(expr) { HRESULT hr = expr; if (FAILED(hr)) throw gcnew ComponentModel::Win32Exception(hr, #expr); }

CComPtr<IDirect3DDevice9Ex> CreateDevice()
{
    CComPtr<IDirect3D9Ex> d3d9Ex;
    AssertHR(Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9Ex.p));

    CComPtr<IDirect3DDevice9Ex> device;
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = TRUE;
    d3dpp.BackBufferHeight = 1;
    d3dpp.BackBufferWidth = 1;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    AssertHR(d3d9Ex->CreateDeviceEx(
        D3DADAPTER_DEFAULT,
        D3DDEVTYPE_HAL,
        NULL,
        D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
        &d3dpp,
        NULL,
        &device.p));

    return device;
}


RenderTarget作成

フォーマットはX8R8G8B8で作成しています。(byte列だとBGRA)

CComPtr<IDirect3DSurface9> CreateRenderTarget(IDirect3DDevice9Ex* device, int width, int height)
{
    CComPtr<IDirect3DSurface9> surface;
    AssertHR(device->CreateRenderTarget(
        width,
        height,
        D3DFMT_X8R8G8B8,
        D3DMULTISAMPLE_NONE,
        0,
        FALSE,
        &surface.p,
        NULL));

    return surface;
}


Renderer作成

今回は赤く塗りつぶすだけの実装になっています。

public ref class Renderer
{
public:
    property IntPtr Surface { IntPtr get() { return IntPtr(_surface); } }
    Renderer(int width, int height)
    {
        _device = CreateDevice();
        _surface = CreateRenderTarget(_device, width, height);

        AssertHR(_device->SetRenderTarget(0, _surface));
        AssertHR(_device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0xff, 0, 0), 1.0f, 0));
    }
private:
    CliComPtr<IDirect3DDevice9Ex> _device;
    CliComPtr<IDirect3DSurface9> _surface;
};


WPF

Xaml上では、Imageコントロールのみを置いています。

C#側でD3DImageを作成し、Image.Sourceにセットしています。

D3DImageではフロントバッファの有効時にも画像が更新されるように実装しています。

Xaml

<Window x:Class="D3DSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="200" Width="250">
    <Grid>
        <Image x:Name="image" />
    </Grid>
</Window>

C#

int width = 100;
int height = 100;
var renderer = new Renderer(width, height);

image.Source = CreateD3DImage(width, height, renderer.Surface);
D3DImage CreateD3DImage(int width, int height, IntPtr surface)
{
    var d3dImage = new D3DImage();
    Action updateImage = () =>
    {
        if (d3dImage.IsFrontBufferAvailable == false)
            return;
        d3dImage.Lock();
        d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface);
        d3dImage.AddDirtyRect(new Int32Rect(0, 0, width, height));
        d3dImage.Unlock();
    };
    d3dImage.IsFrontBufferAvailableChanged += (s, e) => updateImage();
    updateImage();

    return d3dImage;
}

f:id:any-programming:20170502152556p:plain





SSEで画像処理 --- グレースケール化

下記記事にてSSEで簡単な画像処理をしてみました。
SSEで画像処理 --- 導入 - 何でもプログラミング

今回はカラー画像をグレースケール化する処理を実装してみたいと思います。

入力はRGBA32bitを仮定しています。(RGB24bitの場合、48Byte毎に処理する必要があり、さらに複雑になります。)

float化して計算

R、G、Bを各々分解し、係数を掛けて加算を繰り返します。

簡単のため、RGB各々にグレー値を設定して返す内容になっています。(Gray8bitで返す場合は、4 * 16Byteの幅でループを回して結果を詰める必要があります。)

aligned_vector<byte> ToGray(const aligned_vector<byte>& src)
{
    aligned_vector<byte> dst(src.size());

    // 係数
    __m128 rc = _mm_set1_ps(0.299f);
    __m128 gc = _mm_set1_ps(0.587f);
    __m128 bc = _mm_set1_ps(0.114f);

    // マッピング配列(RGBA -> R32, G32, B32)
    __m128i mapR = _mm_setr_epi8(0, -1, -1, -1, 4, -1, -1, -1,  8, -1, -1, -1, 12, -1, -1, -1);
    __m128i mapG = _mm_setr_epi8(1, -1, -1, -1, 5, -1, -1, -1,  9, -1, -1, -1, 13, -1, -1, -1);
    __m128i mapB = _mm_setr_epi8(2, -1, -1, -1, 6, -1, -1, -1, 10, -1, -1, -1, 14, -1, -1, -1);
    for (int i = 0; i < (int)src.size(); i += 16)
    {
        __m128i s = _mm_load_si128((__m128i*)(src.data() + i));

        // RGBA -> R32, G32, B32
        __m128i ri = _mm_shuffle_epi8(s, mapR);
        __m128i gi = _mm_shuffle_epi8(s, mapG);
        __m128i bi = _mm_shuffle_epi8(s, mapB);

        // float化
        __m128 r = _mm_cvtepi32_ps(ri);
        __m128 g = _mm_cvtepi32_ps(gi);
        __m128 b = _mm_cvtepi32_ps(bi);

        // 0.299 * R + 0.587 * G + 0.114 * B
        __m128 gray = _mm_mul_ps(rc, r);
        gray = _mm_add_ps(gray, _mm_mul_ps(gc, g));
        gray = _mm_add_ps(gray, _mm_mul_ps(bc, b));

        // int化
        __m128i grayi = _mm_cvttps_epi32(gray);

        // RGB各々にgray値設定 (gray, 0, 0, 0) -> (gray, gray, gray, 0)
        __m128i d = _mm_add_epi32(grayi, _mm_slli_epi32(grayi, 8));
        d = _mm_add_epi32(d, _mm_slli_epi32(grayi, 16));

        _mm_store_si128((__m128i*)(dst.data() + i), d);
    }
    return dst;
}
f:id:any-programming:20170319005947j:plain f:id:any-programming:20170501160605j:plain


整数演算のみで計算

疑似的に小数演算が出来るよう、大きな定数倍した状態で計算し、最後にその定数で割ります。

定数が大きすぎるとオーバーフローが起きてしまうため、今回は適当に22bitのシフトで計算しています。

aligned_vector<byte> ToGray(const aligned_vector<byte>& src)
{
    aligned_vector<byte> dst(src.size());

    // 係数(小数の係数を定数倍)
    __m128i rc = _mm_set1_epi32((int)((1 << 22) * 0.299));
    __m128i gc = _mm_set1_epi32((int)((1 << 22) * 0.587));
    __m128i bc = _mm_set1_epi32((int)((1 << 22) * 0.114));

    __m128i mapR = _mm_setr_epi8(0, -1, -1, -1, 4, -1, -1, -1,  8, -1, -1, -1, 12, -1, -1, -1);
    __m128i mapG = _mm_setr_epi8(1, -1, -1, -1, 5, -1, -1, -1,  9, -1, -1, -1, 13, -1, -1, -1);
    __m128i mapB = _mm_setr_epi8(2, -1, -1, -1, 6, -1, -1, -1, 10, -1, -1, -1, 14, -1, -1, -1);
    for (int i = 0; i < (int)src.size(); i += 16)
    {
        __m128i s = _mm_load_si128((__m128i*)(src.data() + i));
        __m128i r = _mm_shuffle_epi8(s, mapR);
        __m128i g = _mm_shuffle_epi8(s, mapG);
        __m128i b = _mm_shuffle_epi8(s, mapB);

        __m128i gray = _mm_mullo_epi32(rc, r);
        gray = _mm_add_epi32(gray, _mm_mullo_epi32(gc, g));
        gray = _mm_add_epi32(gray, _mm_mullo_epi32(bc, b));

        // 定数倍されているものを、元に戻す
        gray = _mm_srli_epi32(gray, 22);
        
        __m128i d = _mm_add_epi32(gray, _mm_slli_epi32(gray, 8));
        d = _mm_add_epi32(d, _mm_slli_epi32(gray, 16));

        _mm_store_si128((__m128i*)(dst.data() + i), d);
    }
    return dst;
}


パフォーマンス

float版と整数版では、特に速度の差はありませんでした。

このレベルになるとメモリのバンド幅がボトルネックになるケースもあり、SSE化しなくともマルチスレッド化しただけで最速になることもあります。





SSEで画像処理 --- 導入

下記記事にてアラインされたstd::vectorを作成する方法を記述しました。
アラインされたstd::vector - 何でもプログラミング

今回はそれを利用して、SSEで簡単な画像処理を行ってみます。

準備

今回はintrinsicを用いて実装するため、#include します。

また記述の簡便化のため、aligned_vectorを定義しておきます。

#include <intrin.h>

template<class T> using aligned_vector = std::vector<T, AlignedAllocator<T, 16>>;


ただのコピー

レジスタに転送、取得を行うだけのコードになります。

srcのサイズが16の倍数でない場合は、余りを処理するコードが必要ですが、今回は考慮していません。

aligned_vector<byte> Copy(const aligned_vector<byte>& src)
{
    aligned_vector<byte> dst(src.size());
    for (int i = 0; i < (int)src.size(); i += 16)
    {
        // レジスタに転送
        __m128i data = _mm_load_si128((__m128i*)(src.data() + i));

        // レジスタから取得
        _mm_store_si128((__m128i*)(dst.data() + i), data);
    }
    return dst;
}
f:id:any-programming:20170319005947j:plain f:id:any-programming:20170319005947j:plain


ネガポジ反転

入力データをbit反転することで実現します。

aligned_vector<byte> NegaPosi(const aligned_vector<byte>& src)
{
    aligned_vector<byte> dst(src.size());

    // 全bitが1のデータ
    __m128i c = _mm_set1_epi8((byte)0xff);
    for (int i = 0; i < (int)src.size(); i += 16)
    {
        __m128i px = _mm_load_si128((__m128i*)(src.data() + i));

        // bit反転
        px = _mm_xor_si128(px, c);

        _mm_store_si128((__m128i*)(dst.data() + i), px);
    }
    return dst;
}
f:id:any-programming:20170319005947j:plain f:id:any-programming:20170428192953j:plain


CPUの対応確認

CPUがSSEに対応しているかどうかは、__cpuidを用いて確認できます。

次のコードは一例となります。

int cpuInfo[4];
__cpuid(cpuInfo, 1);
bool sse2  = (cpuInfo[3] >> 26) & 1;
bool sse3  = (cpuInfo[2] >>  0) & 1;
bool sse41 = (cpuInfo[2] >> 19) & 1;
bool sse42 = (cpuInfo[2] >> 20) & 1;
bool avx   = (cpuInfo[2] >> 28) & 1;

詳細は下記ページを参照してください。
__cpuid, __cpuidex