Direct2D 独自Effectの作成(PixelShader利用)

下記記事にて、独自のEffectを作成しました。
Direct2D 独自Effectの作成 - 何でもプログラミング

Effectの効果自体は、組み込みのID2D1OffsetTransformを利用していたので、今回は独自の効果(PixelShader)を定義してみたいと思います。

下図のように、青と赤を入れ替える効果を作成してみます。

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

本記事は上記記事をベースとしていますので、特に説明のない部分は上記記事を参照してみてください。

シェーダコンパイラ

コンパイル済みのcsoを準備するために、今回は動的にコンパイルする関数を用意しました。

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

ComPtr<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
    ComPtr<ID3DBlob> compiled, errorMessage;
    D3DCompile(
        code.c_str(),
        code.size(),
        NULL,
        NULL,
        D3D_COMPILE_STANDARD_FILE_INCLUDE,
        "main",
        target.c_str(),
        flags1,
        0,
        &compiled,
        &errorMessage);
    if (errorMessage)
        throw std::exception((char*)errorMessage->GetBufferPointer());
   
    return compiled;
}


独自Transformクラス

ID2D1DrawTransformを継承して独自のTransformを作成します。

関数がたくさんありますが、重要なのはSetDrawInfoでSetPixelShaderを呼んでいるところくらいです。

PixelShaderの作成自体はEffectクラスのInitializeで行います。

DEFINE_GUID(GUID_MyTransformPS, ....); // 独自のGUIDを定義。

class MyTransform : public ID2D1DrawTransform
{
public:
    MyTransform() : m_refCount(1) {}

    // ここから5つの関数は、ID2D1DrawTransformに必要な関数です。
    IFACEMETHODIMP SetDrawInfo(_In_ ID2D1DrawInfo* pDrawInfo)
    {
        return pDrawInfo->SetPixelShader(GUID_MyTransformPS);
    }
    IFACEMETHODIMP_(UINT32) GetInputCount() const
    {
        return 1;
    }
    IFACEMETHODIMP MapInputRectsToOutputRect(
        _In_reads_(inputRectCount) const D2D1_RECT_L* pInputRects,
        _In_reads_(inputRectCount) const D2D1_RECT_L* pInputOpaqueSubRects,
        UINT32 inputRectCount,
        _Out_ D2D1_RECT_L* pOutputRect,
        _Out_ D2D1_RECT_L* pOutputOpaqueSubRect
    )
    {
        if (inputRectCount != 1)
            return E_INVALIDARG;

        *pOutputRect = pInputRects[0];
        *pOutputOpaqueSubRect = pInputOpaqueSubRects[0];

        return S_OK;
    }
    IFACEMETHODIMP MapOutputRectToInputRects(
        _In_ const D2D1_RECT_L* pOutputRect,
        _Out_writes_(inputRectCount) D2D1_RECT_L* pInputRects,
        UINT32 inputRectCount
    ) const
    {
        if (inputRectCount != 1)
            return E_INVALIDARG;

        pInputRects[0] = *pOutputRect;

        return S_OK;
    }
    IFACEMETHODIMP MapInvalidRect(
        UINT32 inputIndex,
        D2D1_RECT_L invalidInputRect,
        _Out_ D2D1_RECT_L* pInvalidOutputRect
    ) const
    {
        if (inputIndex != 0)
            return E_INVALIDARG;

        *pInvalidOutputRect = invalidInputRect;

        return S_OK;
    }

    // ここからはCOMに必要な関数です。特別な実装はありません。
    IFACEMETHODIMP_(ULONG) AddRef()
    {
        m_refCount++;
        return m_refCount;
    }
    IFACEMETHODIMP_(ULONG) Release()
    {
        m_refCount--;

        if (m_refCount == 0)
            delete this;

        return m_refCount;
    }
    IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _Outptr_ void** ppOutput)
    {
        *ppOutput = nullptr;
        HRESULT hr = S_OK;

        if (riid == __uuidof(ID2D1DrawTransform))
            *ppOutput = (ID2D1DrawTransform*)this;
        else if (riid == __uuidof(ID2D1Transform))
            *ppOutput = (ID2D1Transform*)this;
        else if (riid == __uuidof(ID2D1TransformNode))
            *ppOutput = (ID2D1TransformNode*)this;
        else if (riid == __uuidof(IUnknown))
            *ppOutput = this;
        else
            hr = E_NOINTERFACE;

        if (*ppOutput != nullptr)
            AddRef();

        return hr;
    }
private:
    LONG m_refCount;
};


EffectクラスのInitialize

前回の記事で作成したMyEffectのInitialize関数を、下記で置き換えます。

PixelShaderの入力とTextureは下記の値がDirect2Dから渡されますので、独自の出力を作成します。(VertexShaderを独自で作成した場合は入力を変更できます。)

IFACEMETHODIMP Initialize(_In_ ID2D1EffectContext* pContextInternal, _In_ ID2D1TransformGraph* pTransformGraph)
{
    std::string psCode =
        "Texture2D InputTexture : register(t0);                           \n"
        "SamplerState InputSampler : register(s0);                        \n"
        "                                                                 \n"
        "float4 main(                                                     \n"
        "    float4 clipSpaceOutput : SV_POSITION,                        \n"
        "    float4 sceneSpaceOutput : SCENE_POSITION,                    \n"
        "    float4 texelSpaceInput0 : TEXCOORD0                          \n"
        ") : SV_Target                                                    \n"
        "{                                                                \n"
        "    float4 color = InputTexture.Sample(                          \n"
        "        InputSampler,                                            \n"
        "        texelSpaceInput0.xy                                      \n"
        "    );                                                           \n"
        "    return float4(color.b, color.g, color.r, color.a);           \n"
        "}                                                                \n"
        ;
    auto cso = Compile(psCode, "ps_4_0_level_9_1");
    auto hr = pContextInternal->LoadPixelShader(GUID_MyTransformPS, (BYTE*)cso->GetBufferPointer(), cso->GetBufferSize());
    if (FAILED(hr))
        return hr;

    ComPtr<MyTransform> transform;
    transform.Attach(new MyTransform());
    return pTransformGraph->SetSingleTransformNode(transform.Get());
}