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(); } } } }
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)を定義してみたいと思います。
下図のように、青と赤を入れ替える効果を作成してみます。
本記事は上記記事をベースとしていますので、特に説明のない部分は上記記事を参照してみてください。
シェーダコンパイラ
コンパイル済みの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効果を利用して、描画結果をずらしてみます。
本記事は上記記事をベースとしていますので、特に言及のない関数は上記記事を参照してみてください。
独自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を適用してみます。
記事中に特に説明のない関数は、上記記事を参照してみてください。
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, ¶ms)); } 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を用いて上記記事と同じアプリを作成してみたいと思います。
記事中の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, ¶ms)); } 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; }