I rewrote it with ID2D1DeviceContext from a Win32 template I use for tests (there is just a warning not really important :
DXGI WARNING: IDXGIFactory::CreateSwapChain/IDXGISwapChain::ResizeBuffers: The buffer height inferred from the output window is zero. Taking 8 as a reasonable default instead [ MISCELLANEOUS WARNING #2) :
#include <windows.h>
#include <tchar.h>
//#include <commctrl.h>
//#pragma comment (lib, "comctl32")
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#include <d3d11.h>
#pragma comment (lib, "D3D11")
#include <d2d1.h>
#pragma comment (lib, "D2d1")
#include <d2d1_1.h> // ID2D1Factory1
#include <dxgi1_2.h>
#include <dwrite.h>
#pragma comment(lib,"Dwrite")
template <class T> void SafeRelease(T** ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
HINSTANCE hInst;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int nWidth = 600, nHeight = 400;
ID2D1Factory* m_pD2DFactory = NULL;
ID2D1Factory1* m_pD2DFactory1 = NULL;
IDWriteFactory* m_pDWriteFactory = NULL;
ID3D11Device* m_pD3D11Device = NULL;
ID3D11DeviceContext* m_pD3D11DeviceContext = NULL;
IDXGIDevice1* m_pDXGIDevice = NULL;
ID2D1Device* m_pD2DDevice = NULL;
ID2D1DeviceContext* m_pD2DDeviceContext = NULL;
ID2D1SolidColorBrush* m_pD2DMainBrush = NULL;
IDWriteTextFormat* m_pTextFormat = NULL;
IDWriteTextLayout* m_pTextLayout = NULL;
IDXGISwapChain1* m_pDXGISwapChain1 = NULL;
ID2D1Bitmap1* m_pD2DTargetBitmap = NULL;
HRESULT CreateD2D1Factory();
HRESULT CreateDWriteFactory();
HRESULT CreateD3D11Device();
HRESULT CreateDeviceResources();
HRESULT CreateSwapChain(HWND hWnd);
HRESULT ConfigureSwapChain(HWND hWnd);
void Clean();
WCHAR m_wsFont[] = L"Arial";
FLOAT m_nFontSize = 15.0F;
WCHAR m_wsText[] = L"Test";
HRESULT OnPaint(HWND hWnd);
void OnResize(HWND hWnd, UINT nWidth, UINT nHeight);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
hInst = hInstance;
WNDCLASSEX wcex =
{
sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInst, LoadIcon(NULL, IDI_APPLICATION),
LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW + 1)/*(HBRUSH)GetStockObject(BLACK_BRUSH)*/, NULL, TEXT("WindowClass"), NULL,
};
if (!RegisterClassEx(&wcex))
return MessageBox(NULL, TEXT("Cannot register class !"), TEXT("Error"), MB_ICONERROR | MB_OK);
int nX = (GetSystemMetrics(SM_CXSCREEN) - nWidth) / 2, nY = (GetSystemMetrics(SM_CYSCREEN) - nHeight) / 2;
HWND hWnd = CreateWindowEx(0, wcex.lpszClassName, TEXT("Direct2D Test DrawTextLayout"), WS_OVERLAPPEDWINDOW, nX, nY, nWidth, nHeight, NULL, NULL, hInst, NULL);
if (!hWnd)
return MessageBox(NULL, TEXT("Cannot create window !"), TEXT("Error"), MB_ICONERROR | MB_OK);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
HRESULT hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
hr = CreateD2D1Factory();
if (SUCCEEDED(hr))
{
hr = CreateDWriteFactory();
if (SUCCEEDED(hr))
{
hr = CreateD3D11Device();
hr = CreateDeviceResources();
hr = CreateSwapChain(hWnd);
if (SUCCEEDED(hr))
hr = ConfigureSwapChain(hWnd);
}
}
}
return 0;
}
break;
case WM_PAINT:
{
return OnPaint(hWnd);
}
break;
case WM_SIZE:
{
UINT nWidth = LOWORD(lParam);
UINT nHeight = HIWORD(lParam);
OnResize(hWnd, nWidth, nHeight);
return 0;
}
break;
case WM_DESTROY:
{
Clean();
CoUninitialize();
PostQuitMessage(0);
return 0;
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
HRESULT CreateD2D1Factory()
{
HRESULT hr = S_OK;
D2D1_FACTORY_OPTIONS options;
ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));
options.debugLevel = D2D1_DEBUG_LEVEL::D2D1_DEBUG_LEVEL_INFORMATION;
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, options, &m_pD2DFactory);
if (SUCCEEDED(hr))
hr = m_pD2DFactory->QueryInterface((ID2D1Factory1**)&m_pD2DFactory1);
return hr;
}
HRESULT CreateDWriteFactory()
{
HRESULT hr = S_OK;
hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE::DWRITE_FACTORY_TYPE_SHARED, __uuidof(m_pDWriteFactory), reinterpret_cast<IUnknown**>(&m_pDWriteFactory));
return hr;
}
// https://learn.microsoft.com/en-us/windows/win32/direct2d/devices-and-device-contexts
HRESULT CreateD3D11Device()
{
HRESULT hr = S_OK;
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
// This array defines the set of DirectX hardware feature levels this app supports.
// The ordering is important and you should preserve it.
// Don't forget to declare your app's minimum required feature level in its
// description. All apps are assumed to support 9.1 unless otherwise stated.
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
// Create the DX11 API device object, and get a corresponding context.
D3D_FEATURE_LEVEL featureLevel;
hr = D3D11CreateDevice(
NULL, // specify null to use the default adapter
D3D_DRIVER_TYPE_HARDWARE,
0,
creationFlags, // optionally set debug and Direct2D compatibility flags
featureLevels, // list of feature levels this app can support
ARRAYSIZE(featureLevels), // number of possible feature levels
D3D11_SDK_VERSION,
&m_pD3D11Device, // returns the Direct3D device created
&featureLevel, // returns feature level of device created
&m_pD3D11DeviceContext // returns the device immediate context
);
if (SUCCEEDED(hr))
{
// Obtain the underlying DXGI device of the Direct3D11 device.
hr = m_pD3D11Device->QueryInterface((IDXGIDevice1**)&m_pDXGIDevice);
if (SUCCEEDED(hr))
{
// Obtain the Direct2D device for 2-D rendering.
hr = m_pD2DFactory1->CreateDevice(m_pDXGIDevice, &m_pD2DDevice);
if (SUCCEEDED(hr))
{
// Get Direct2D device's corresponding device context object.
hr = m_pD2DDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &m_pD2DDeviceContext);
if (SUCCEEDED(hr))
{
m_pD2DDeviceContext->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
m_pD2DDeviceContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_DEFAULT);
}
}
}
}
return hr;
}
HRESULT CreateFormatAndLayout()
{
HRESULT hr = S_OK;
if (m_pTextFormat == nullptr)
{
DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_NORMAL;
DWRITE_FONT_STYLE style = DWRITE_FONT_STYLE_NORMAL;
hr = m_pDWriteFactory->CreateTextFormat(m_wsFont, NULL, weight, style, DWRITE_FONT_STRETCH_NORMAL, m_nFontSize, L"", &m_pTextFormat);
if (SUCCEEDED(hr))
{
hr = m_pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
hr = m_pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
hr = m_pDWriteFactory->CreateTextLayout(m_wsText, ARRAYSIZE(m_wsText), m_pTextFormat, 500, 100, &m_pTextLayout);
if (SUCCEEDED(hr))
{
IDWriteTypography* pTypography = nullptr;
hr = m_pDWriteFactory->CreateTypography(&pTypography);
if (SUCCEEDED(hr))
{
DWRITE_FONT_FEATURE ff =
{
DWRITE_FONT_FEATURE_TAG_STYLISTIC_SET_7,
1
};
hr = pTypography->AddFontFeature(ff);
if (SUCCEEDED(hr))
{
DWRITE_TEXT_RANGE tr = { 0, ARRAYSIZE(m_wsText) };
m_pTextLayout->SetTypography(pTypography, tr);
}
SafeRelease(&pTypography);
}
}
}
}
return hr;
}
HRESULT CreateDeviceResources()
{
HRESULT hr = S_OK;
if (m_pD2DMainBrush == NULL)
{
hr = m_pD2DDeviceContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &m_pD2DMainBrush);
}
hr = CreateFormatAndLayout();
return hr;
}
HRESULT CreateSwapChain(HWND hWnd)
{
HRESULT hr = S_OK;
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
swapChainDesc.Width = 1;
swapChainDesc.Height = 1;
/*RECT rc;
GetClientRect(m_hStatic, &rc);
swapChainDesc.Width = rc.right - rc.left;
swapChainDesc.Height = rc.bottom - rc.top;*/
swapChainDesc.Format = DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM;
swapChainDesc.Stereo = false;
swapChainDesc.SampleDesc.Count = 1; // don't use multi-sampling
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2; // use double buffering to enable flip
swapChainDesc.Scaling = (hWnd != NULL) ? DXGI_SCALING::DXGI_SCALING_NONE : DXGI_SCALING::DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
swapChainDesc.Flags = 0;
IDXGIAdapter* pDXGIAdapter = nullptr;
hr = m_pDXGIDevice->GetAdapter(&pDXGIAdapter);
if (SUCCEEDED(hr))
{
IDXGIFactory2* pDXGIFactory2 = nullptr;
hr = pDXGIAdapter->GetParent(IID_PPV_ARGS(&pDXGIFactory2));
if (SUCCEEDED(hr))
{
if (hWnd != NULL)
{
hr = pDXGIFactory2->CreateSwapChainForHwnd(m_pD3D11Device, hWnd, &swapChainDesc, nullptr, nullptr, &m_pDXGISwapChain1);
}
else
{
hr = pDXGIFactory2->CreateSwapChainForComposition(m_pD3D11Device, &swapChainDesc, nullptr, &m_pDXGISwapChain1);
}
if (SUCCEEDED(hr))
hr = m_pDXGIDevice->SetMaximumFrameLatency(1);
SafeRelease(&pDXGIFactory2);
}
SafeRelease(&pDXGIAdapter);
}
return hr;
}
HRESULT ConfigureSwapChain(HWND hWnd)
{
HRESULT hr = S_OK;
D2D1_BITMAP_PROPERTIES1 bitmapProperties = D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS::D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS::D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
D2D1::PixelFormat(DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE::D2D1_ALPHA_MODE_IGNORE),
0,
0,
NULL
);
unsigned int nDPI = GetDpiForWindow(hWnd);
bitmapProperties.dpiX = nDPI;
bitmapProperties.dpiY = nDPI;
IDXGISurface* pDXGISurface;
if (m_pDXGISwapChain1)
{
hr = m_pDXGISwapChain1->GetBuffer(0, IID_PPV_ARGS(&pDXGISurface));
if (SUCCEEDED(hr))
{
hr = m_pD2DDeviceContext->CreateBitmapFromDxgiSurface(pDXGISurface, bitmapProperties, &m_pD2DTargetBitmap);
if (SUCCEEDED(hr))
{
m_pD2DDeviceContext->SetTarget(m_pD2DTargetBitmap);
}
SafeRelease(&pDXGISurface);
}
}
return hr;
}
HRESULT OnPaint(HWND hWnd)
{
HRESULT hr = S_OK;
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hWnd, &ps);
if (m_pD2DDeviceContext && m_pDXGISwapChain1)
{
m_pD2DDeviceContext->BeginDraw();
D2D1_SIZE_F size = m_pD2DDeviceContext->GetSize();
m_pD2DDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::White));
//m_pD2DDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::LightCoral));
if (m_pTextLayout)
{
D2D1_POINT_2F pt = D2D1::Point2F(0, 0);
m_pTextLayout->SetMaxWidth(size.width);
m_pTextLayout->SetMaxHeight(size.height);
m_pD2DDeviceContext->DrawTextLayout(pt, m_pTextLayout, m_pD2DMainBrush, D2D1_DRAW_TEXT_OPTIONS_NO_SNAP | D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
}
hr = m_pD2DDeviceContext->EndDraw();
if (hr == D2DERR_RECREATE_TARGET)
{
m_pD2DDeviceContext->SetTarget(NULL);
SafeRelease(&m_pD2DDeviceContext);
hr = CreateD3D11Device();
hr = CreateDeviceResources();
hr = CreateSwapChain(hWnd);
if (SUCCEEDED(hr))
hr = ConfigureSwapChain(hWnd);
}
hr = m_pDXGISwapChain1->Present(1, 0);
}
EndPaint(hWnd, &ps);
InvalidateRect(hWnd, NULL, FALSE);
return hr;
}
void OnResize(HWND hWnd, UINT nWidth, UINT nHeight)
{
if (m_pDXGISwapChain1)
{
HRESULT hr = S_OK;
m_pD2DDeviceContext->SetTarget(nullptr);
SafeRelease(&m_pD2DTargetBitmap);
hr = m_pDXGISwapChain1->ResizeBuffers(
2, // Double-buffered swap chain.
nWidth,
nHeight,
DXGI_FORMAT_B8G8R8A8_UNORM,
0
);
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
{
CreateD3D11Device();
CreateSwapChain(hWnd);
}
else
{
//DX::ThrowIfFailed(hr);
}
ConfigureSwapChain(hWnd);
}
}
void CleanDeviceResources()
{
//SafeRelease(&m_pD2DBitmap);
SafeRelease(&m_pD2DMainBrush);
SafeRelease(&m_pTextFormat);
SafeRelease(&m_pTextLayout);
}
void Clean()
{
SafeRelease(&m_pD2DDevice);
SafeRelease(&m_pD2DDeviceContext);
SafeRelease(&m_pD2DTargetBitmap);
CleanDeviceResources();
SafeRelease(&m_pDXGISwapChain1);
SafeRelease(&m_pDXGIDevice);
SafeRelease(&m_pD3D11Device);
SafeRelease(&m_pD3D11DeviceContext);
SafeRelease(&m_pDWriteFactory);
SafeRelease(&m_pD2DFactory1);
SafeRelease(&m_pD2DFactory);
}