Garbage Collectionを検知する(C#)

通常のプログラミングにおいて、いつGCされたか気にする必要はありませんが、極まれにGCのタイミングで処理をしたい時があります(設計で回避するべきではありますが…)

今回はGCを検知する方法を調べてみました。

GC.WaitForFullGCComplete

GCクラスの関数を利用する方法です。

これを利用するには、App.configで下記が指定されている必要があります。(コンカレントGCを無効化)

<configuration>
  <runtime>
    <gcConcurrent enabled="false" />
  </runtime>
</configuration>


GCNotifierクラスを定義してみます。

GC.RegisterForFullGCNotificationで通知を有効化し、GC.WaitForFullGCApproachとGC.WaitForFullGCCompleteで通知を待ちます。

GC.RegisterForFullGCNotificationの引数は、大きいほど通知を受け取りやすくなるそうです。

static class GCNotifier
{
    public static event Action Collected;
    public static void Start()
    {
        GC.RegisterForFullGCNotification(10, 10);
        Task.Run(() =>
        {
            while (true)
            {
                GC.WaitForFullGCApproach();
                if (GC.WaitForFullGCComplete() == GCNotificationStatus.Succeeded)
                    Collected?.Invoke();
            }
        });
    }
}


利用は下記のようになります。

GCNotifier.Collected += () => Console.WriteLine("collected");
GCNotifier.Start();


Finalizeを利用

下記のものを簡略化してみます。
Jeffrey Richter: Receiving notifications when garbage collections occur – Microsoft Press blog

DummyクラスのFinalizeでコールバックを呼び、さらに新しいDummyオブジェクトを生成しています。

GCNotifierクラスは下記のようになり、利用方法はGC.WaitForFullGCCompleteの時と同じです。

static class GCNotifier
{
    public static event Action Collected;
    public static void Start()
    {
        new DummyObject();
    }
    class DummyObject
    {
        ~DummyObject()
        {
            if (!AppDomain.CurrentDomain.IsFinalizingForUnload()
            &&  !Environment.HasShutdownStarted)
            {
                Collected?.Invoke();
                new DummyObject();
            }
        }
    }
}


比較

GC.WaitForFullGCCompleteは、GCの後と前で処理を追加できるのが利点ですが、コンカレントGCが使えないのと、全てのGCで通知が来るわけではないのが欠点です。

Finalize利用は、全てのGCを検知できるのが利点ですが、GC前に処理が追加できないのが欠点です。





F#でメモ化(ConditionalWeakTable)

プログラムの高速化のため、一度計算した値を再利用することはしばしばあります。

しかし結果を状態として保存する必要があるため、F#では少し扱いにくいです。

そこで、ある関数を結果を再利用する関数に変換する、memoize関数を定義してみたいと思います。

memoize関数

入力と結果をDictionaryに保存しておく関数に変換しています。

またConditionalWeakTableを利用することにより、selectKeyで寿命を共にするオブジェクトを指定できるようになっています。

let memoize (f : 'Args -> 'Result) (selectKey : 'Args -> 'Key) = 
    let table = ConditionalWeakTable<'Key, Dictionary<'Args, 'Result>>()
    fun (args : 'Args) ->
        let key = selectKey args
        let dictionary = table.GetOrCreateValue(key)
        if dictionary.ContainsKey(args) = false then
            dictionary.[args] <- f args
        dictionary.[args]

利用しやすいように、複数入力に対応した関数を用意しておきます。(寿命は第一引数と同じになります。)

let memoize1 (f : 'Arg -> 'Result) =
    memoize f id

let memoize2 (f : 'Arg1 -> 'Arg2 -> 'Result) =
    let tupled = memoize (fun (x1, x2) -> f x1 x2) fst
    fun x1 x2 -> tupled (x1, x2)

let memoize3 (f : 'Arg1 -> 'Arg2 -> 'Arg3 -> 'Result) =
    let tupled = memoize (fun (x1, x2, x3) -> f x1 x2 x3) (fun (x, _, _) -> x)
    fun x1 x2 x3 -> tupled (x1, x2, x3)


実際の利用

下記のようなNameレコードを例にしてみます。

フルネームを作成するfull関数は、二度目以降は結果を再利用しています。

また計算結果はNameオブジェクトと寿命が同じになります。

type Name =
    { First : string
      Last : string
    }
module Name =
    let full = memoize1 (fun name -> 
        printf "called\n" 
        name.First + name.Last)
let name = { First = "John"; Last = "Smith" }
let full = Name.full name // called
let full2 = Name.full name


入力に参照型がない場合

今回はConditionalWeakTableを利用したため、寿命を共にする参照型の入力が必要となります。

寿命がなく、入力と結果が共にサイズが小さいのであれば、Dictionaryで管理してもいいと思われます。





ネストしたレコードの更新(Functional lenses)

下記記事にて、リフレクションを用いてネストしたレコードの更新を実装してみました。
ネストしたレコードの更新 (F#) - 何でもプログラミング

今回はFunctional lensesを利用して同様の内容を実装してみたいと思います。

Lens型定義

Lens型は対象オブジェクトから値を取得するGetと、値を更新した新しいオブジェクトを返すSetからなります。

type Lens<'Type, 'Field> =
    { Get : 'Type -> 'Field
      Set : 'Field -> 'Type -> 'Type
    }


Lens関数群定義

Lensを合成するcomposeが最も重要なものとなります。

module Lens =
    let create get set = 
        { Get = get; Set = set; }

    let set (lens : Lens<'a, 'b>) (value : 'b) (object : 'a) =
        lens.Set value object

    let update (lens : Lens<'a, 'b>) (f : 'b -> 'b) (object : 'a) =
        lens.Set (lens.Get object |> f) object

    let compose (lens1 : Lens<'a, 'b>) (lens2 : Lens<'b, 'c>) =
        { Get = lens1.Get >> lens2.Get
          Set = lens2.Set >> update lens1
        }

// composeを楽にする演算子
let inline (>-) (lens1 : Lens<'a, 'b>) (lens2 : Lens<'b, 'c>) =
    Lens.compose lens1 lens2


実際の利用

下記のレコードが定義されているとします。

type Container = { Value : int }
type Child = { Container : Container }
type Root = { Child : Child }

let record1 = { Child = { Container = { Value = 100 } } }

まずは各レコードのLensを定義します。

module Container =
    let value = Lens.create (fun x -> x.Value) (fun x y -> { y with Value = x })
module Child =
    let container = Lens.create (fun x -> x.Container) (fun x y -> { y with Container = x })
module Root = 
    let child = Lens.create (fun x -> x.Child) (fun x y -> { y with Child = x })

レコードの更新は、下記のように記述します。

現在値を参照する場合はLens.updateを利用します。

let record2 = record1 |> Lens.set (Root.child >- Child.container >- Container.value) 200


リフレクションと比較

リフレクションを利用したほうがLensの定義も必要なく簡潔に記述できます。

一方Lensを利用するとパフォーマンスを期待でき、リフレクションのない言語でも応用可能です。





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());
}






Direct2D 独自Effectの作成

下記の記事にて、Direct2Dの組み込みのEffectを利用してみました。
Direct2D Effectの利用 - 何でもプログラミング

今回は独自のEffectを作成してみたいと思います。

組み込みで用意されているOffset効果を利用して、描画結果をずらしてみます。

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

本記事は上記記事をベースとしていますので、特に言及のない関数は上記記事を参照してみてください。

独自Effectクラス

ID2D1EffectImplを実装して独自のEffectクラスを作成します。

基本的にはInitialize関数とRegister関数の実装がメインとなります。

#include <initguid.h>
#include <d2d1effectauthor.h>

DEFINE_GUID(CLSID_MyEffect, ...); // 独自のGUIDを定義してください。

class MyEffect : public ID2D1EffectImpl
{
public:
    // ここから3つの関数は、ID2D1EffectImplに必要な関数です。
    IFACEMETHODIMP Initialize(_In_ ID2D1EffectContext* pContextInternal, _In_ ID2D1TransformGraph* pTransformGraph)
    {
        // Offsetを行う効果(組み込み)の作成&登録
        ComPtr<ID2D1OffsetTransform> transform;
        auto hr = pContextInternal->CreateOffsetTransform(D2D1::Point2L(10, 10), &transform);
        if (SUCCEEDED(hr))
            hr = pTransformGraph->SetSingleTransformNode(transform.Get());

        return hr;
    }
    IFACEMETHODIMP PrepareForRender(D2D1_CHANGE_TYPE changeType)
    {
        return S_OK;
    }
    IFACEMETHODIMP SetGraph(_In_ ID2D1TransformGraph* pGraph)
    {
        return S_OK;
    }

    // ここから2つのstatic関数は、このクラスをID2D1Factory1に登録するのに利用します。(関数名は何でも大丈夫です。)
    static HRESULT Register(_In_ ID2D1Factory1* pFactory)
    {
        // 1画像を入力とするEffectの登録
        auto xml = 
            L"<?xml version = '1.0'?>                                                       "
            L"<Effect>                                                                      "
            L"    <Property name='DisplayName' type='string' value='MyEffect' />            "
            L"    <Property name='Author' type='string' value='Author' />                   "
            L"    <Property name='Category' type='string' value='Category' />               "
            L"    <Property name='Description' type='string' value='Description' />         "
            L"    <Inputs>                                                                  "
            L"        <Input name='Source' />                                               "
            L"    </Inputs>                                                                 "
            L"</Effect>                                                                     "
            ;
        return pFactory->RegisterEffectFromString(CLSID_MyEffect, xml, nullptr, 0, CreateEffect);
    }
    static HRESULT __stdcall CreateEffect(_Outptr_ IUnknown** ppEffectImpl)
    {
        *ppEffectImpl = (ID2D1EffectImpl*)(new MyEffect());

        if (*ppEffectImpl == nullptr)
            return E_OUTOFMEMORY;

        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(ID2D1EffectImpl))
            *ppOutput = (ID2D1EffectImpl*)this;
        else if (riid == __uuidof(IUnknown))
            *ppOutput = this;
        else
            hr = E_NOINTERFACE;

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

        return hr;
    }
private:
    LONG m_refCount;
    MyEffect() : m_refCount(1) {}
};


Effectの作成

独自のGUIDでCreateEffectします。

ComPtr<ID2D1Effect> CreateEffect(ID2D1DeviceContext* context)
{
    ComPtr<ID2D1Effect> effect;
    AssertHR(context->CreateEffect(CLSID_MyEffect, &effect));

    return effect;
}


main関数

作成したEffectの登録が追記されています。

ID2D1Factory1を作成した時に同時に登録したほうが簡潔だと思われます。

int main()
{
    HWND hwnd = CreateHWND(WndProc);

    auto dxgiDevice = CreateDXGIDevice();
    g_context = CreateD2DContext(dxgiDevice.Get());
    g_swapChain = CreateSwapChain(dxgiDevice.Get(), hwnd);
    g_backBuffer = CreateBackBufferBitmap(g_context.Get(), g_swapChain.Get());
    g_offscreenBuffer = CreateTargetBitmap(g_context.Get(), g_backBuffer->GetPixelSize());

    ComPtr<ID2D1Factory> factory;
    g_context->GetFactory(&factory);
    ComPtr<ID2D1Factory1> factory1;
    AssertHR(factory.As(&factory1));
    AssertHR(MyEffect::Register(factory1.Get()));

    g_effect = CreateEffect(g_context.Get());

    Run(hwnd);

    return 0;
}






Direct2D Effectの利用

下記記事にて、ID2D1DeviceContextを利用して楕円を描画するところまで実装しました。
Direct2D導入(ID2D1DeviceContext) - 何でもプログラミング

今回はID2D1DeviceContextで利用できるようになったEffectを実装してみたいと思います。

上記記事で描画した楕円画像に、赤と青を入れ替えるEffectを適用してみます。

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

記事中に特に説明のない関数は、上記記事を参照してみてください。

Effect作成

既存のEffectを利用して、赤と青を入れ替えるEffectを作成します。

#include <initguid.h>
#include <d2d1_1.h>
#pragma comment(lib, "d2d1.lib")

ComPtr<ID2D1Effect> CreateEffect(ID2D1DeviceContext* context)
{
    ComPtr<ID2D1Effect> effect;
    AssertHR(context->CreateEffect(CLSID_D2D1ColorMatrix, &effect));

    D2D1_MATRIX_5X4_F matrix = D2D1::Matrix5x4F(0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0);
    AssertHR(effect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, matrix));

    return effect;
}


OffscreenBufferの作成

Effectの入力としてBackBufferは利用できないため、一時描画先としてOffscreenBufferを作成します。

ComPtr<ID2D1Bitmap1> CreateTargetBitmap(ID2D1DeviceContext* context, D2D1_SIZE_U size)
{
    auto prop = D2D1::BitmapProperties1(
        D2D1_BITMAP_OPTIONS_TARGET,
        D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)
    );

    ComPtr<ID2D1Bitmap1> bitmap;
    AssertHR(context->CreateBitmap(size, nullptr, size.width * 4, prop, &bitmap));

    return bitmap;
}


アプリケーションコード

まずOffscreenBufferに楕円を描画し、次にEffectを適用した結果をBackBufferに描画しています。

サイズが変更された場合は、BackBuffer、OffscreenBuffer共に作り直しています。(BackBufferを解放しておかないと、ResizeBuffersで例外が発生します。)

ComPtr<ID2D1DeviceContext> g_context;
ComPtr<IDXGISwapChain1> g_swapChain;
ComPtr<ID2D1Bitmap1> g_backBuffer;
ComPtr<ID2D1Bitmap1> g_offscreenBuffer;
ComPtr<ID2D1Effect> g_effect;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_PAINT:
        {
            g_context->BeginDraw();

            g_context->SetTarget(g_offscreenBuffer.Get());

            g_context->Clear(D2D1::ColorF(D2D1::ColorF::LightYellow));

            // 楕円描画
            D2D1_SIZE_F size = g_context->GetSize();
            float rx = size.width / 2;
            float ry = size.height / 2;
            D2D1_ELLIPSE ellipse = D2D1::Ellipse(D2D1::Point2F(rx, ry), rx, ry);
            ComPtr<ID2D1SolidColorBrush> brush;
            AssertHR(g_context->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Orange), &brush));
            g_context->FillEllipse(ellipse, brush.Get());

            // Effect適用(赤青いれかえ)
            g_context->SetTarget(g_backBuffer.Get());
            g_effect->SetInput(0, g_offscreenBuffer.Get());
            g_context->DrawImage(g_effect.Get());
            g_effect->SetInput(0, nullptr);

            AssertHR(g_context->EndDraw());
            g_context->SetTarget(nullptr);
                
            DXGI_PRESENT_PARAMETERS params = { 0 };
            AssertHR(g_swapChain->Present1(1, 0, &params));
        }
        return 0;
    case WM_SIZE:
        g_backBuffer = nullptr;
        AssertHR(g_swapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0)); // サイズ0で、自動でWindowのサイズが設定される
        g_backBuffer = CreateBackBufferBitmap(g_context.Get(), g_swapChain.Get());
        g_offscreenBuffer = CreateTargetBitmap(g_context.Get(), g_backBuffer->GetPixelSize());
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}
int main()
{
    HWND hwnd = CreateHWND(WndProc);

    auto dxgiDevice = CreateDXGIDevice();
    g_context = CreateD2DContext(dxgiDevice.Get());
    g_swapChain = CreateSwapChain(dxgiDevice.Get(), hwnd);
    g_backBuffer = CreateBackBufferBitmap(g_context.Get(), g_swapChain.Get());
    g_offscreenBuffer = CreateTargetBitmap(g_context.Get(), g_backBuffer->GetPixelSize());
    g_effect = CreateEffect(g_context.Get());

    Run(hwnd);

    return 0;
}






Direct2D導入(ID2D1DeviceContext)

下記記事にて、ID2D1HwndRenderTargetを利用してDirect2Dを利用してみました。
Direct2D導入(ID2D1HwndRenderTarget) - 何でもプログラミング

Direct2D 1.1からは、ID2D1DeviceContextを利用して実装するようになっています。

今回はID2D1DeviceContextを用いて上記記事と同じアプリを作成してみたいと思います。

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

記事中のCreateHWND関数、Run関数は上記記事を参照してください。

IDXGIDevice1の作成

まずはIDXGIDevice1を作成します。

ID3D11Deviceを作成して、そこからIDXGIDevice1を取得します。

#include <dxgi1_2.h>

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

ComPtr<IDXGIDevice1> CreateDXGIDevice()
{
    ComPtr<ID3D11Device> device;
    AssertHR(D3D11CreateDevice(
        nullptr,
        D3D_DRIVER_TYPE_HARDWARE,
        0,
        D3D11_CREATE_DEVICE_BGRA_SUPPORT, // Direct2Dではこのフラグが必要
        nullptr,
        0,
        D3D11_SDK_VERSION,
        &device,
        nullptr,
        nullptr
    ));

    ComPtr<IDXGIDevice1> dxgiDevice;
    AssertHR(device.As(&dxgiDevice));
    AssertHR(dxgiDevice->SetMaximumFrameLatency(1)); // レイテンシを最小にしておく

    return dxgiDevice;
}


ID2D1DeviceContextの作成

IDXGIDevice1を利用して、ID2D1DeviceContextを作成します。

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

ComPtr<ID2D1DeviceContext> CreateD2DContext(IDXGIDevice1* dxgiDevice)
{
    ComPtr<ID2D1Factory1> factory;
    AssertHR(D2D1CreateFactory<ID2D1Factory1>(D2D1_FACTORY_TYPE_SINGLE_THREADED, &factory));

    ComPtr<ID2D1Device> device;
    AssertHR(factory->CreateDevice(dxgiDevice, &device));

    ComPtr<ID2D1DeviceContext> context;
    AssertHR(device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &context));

    return context;
}


IDXGISwapChain1の作成

Windowに描画結果を表示するため、SwapChainを作成します。

またSwapChainのBackBufferのID2D1Bitmap1を作成する関数も準備します。

ComPtr<IDXGISwapChain1> CreateSwapChain(IDXGIDevice1* device, HWND hwnd)
{
    ComPtr<IDXGIAdapter> adapter;
    AssertHR(device->GetAdapter(&adapter));

    ComPtr<IDXGIFactory2> factory;
    AssertHR(adapter->GetParent(IID_PPV_ARGS(&factory)));

    DXGI_SWAP_CHAIN_DESC1 desc = { 0 };
    desc.Width = 0; // 自動でWindowのサイズが設定される
    desc.Height = 0;
    desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    desc.Stereo = false;
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    desc.BufferCount = 2;
    desc.Scaling = DXGI_SCALING_NONE;
    desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
    desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
    desc.Flags = 0;

    ComPtr<IDXGISwapChain1> swapChain;
    AssertHR(factory->CreateSwapChainForHwnd(
        device,
        hwnd,
        &desc,
        nullptr,
        nullptr,
        &swapChain
    ));

    return swapChain;
}
ComPtr<ID2D1Bitmap1> CreateBackBufferBitmap(ID2D1DeviceContext* context, IDXGISwapChain1* swapChain)
{
    ComPtr<IDXGISurface> backBuffer;
    AssertHR(swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)));

    auto prop = D2D1::BitmapProperties1(
        D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
        D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)
    );
    ComPtr<ID2D1Bitmap1> bitmap;
    AssertHR(context->CreateBitmapFromDxgiSurface(backBuffer.Get(), &prop, &bitmap));

    return bitmap;
}


楕円描画関数

Windowいっぱいに楕円を描画します。

void Draw(ID2D1DeviceContext* context)
{
    context->BeginDraw();

    context->Clear(D2D1::ColorF(D2D1::ColorF::LightYellow));

    D2D1_SIZE_F size = context->GetSize();
    float rx = size.width / 2;
    float ry = size.height / 2;
    D2D1_ELLIPSE ellipse = D2D1::Ellipse(D2D1::Point2F(rx, ry), rx, ry);
    ComPtr<ID2D1SolidColorBrush> brush;
    AssertHR(context->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Orange), &brush));
    context->FillEllipse(ellipse, brush.Get());

    AssertHR(context->EndDraw());
}


アプリケーションコード

Drawで描画した際、Windowに表示するためにSwapChainのPresentを呼び出す必要があります。

サイズ変更は、一度ContextのTargetから外し、ResizeBuffersで変更した後、再度Targetに設定する必要があります。

ComPtr<ID2D1DeviceContext> g_context;
ComPtr<IDXGISwapChain1> g_swapChain;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_PAINT:
        {
            Draw(g_context.Get());
            DXGI_PRESENT_PARAMETERS params = { 0 };
            AssertHR(g_swapChain->Present1(1, 0, &params));
        }
        return 0;
    case WM_SIZE:
        g_context->SetTarget(nullptr);
        AssertHR(g_swapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0)); // サイズ0で、自動でWindowのサイズが設定される
        g_context->SetTarget(CreateBackBufferBitmap(g_context.Get(), g_swapChain.Get()).Get());
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}
int main()
{
    HWND hwnd = CreateHWND(WndProc);

    auto dxgiDevice = CreateDXGIDevice();
    g_context = CreateD2DContext(dxgiDevice.Get());
    g_swapChain = CreateSwapChain(dxgiDevice.Get(), hwnd);
    g_context->SetTarget(CreateBackBufferBitmap(g_context.Get(), g_swapChain.Get()).Get());

    Run(hwnd);

    return 0;
}