Skip to content

Instantly share code, notes, and snippets.

@nanoant
Last active August 16, 2022 01:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nanoant/dde75805132561140ed5e38f4048f5c1 to your computer and use it in GitHub Desktop.
Save nanoant/dde75805132561140ed5e38f4048f5c1 to your computer and use it in GitHub Desktop.
DirectWrite rendering demonstration using Win32 API
// just build with: cl Win32DirectWrite.cpp
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <ShellScalingApi.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")
// Include the v6 common controls in the manifest
#pragma comment(linker, \
"\"/manifestdependency:type='Win32' "\
"name='Microsoft.Windows.Common-Controls' "\
"version='6.0.0.0' "\
"processorArchitecture='*' "\
"publicKeyToken='6595b64144ccf1df' "\
"language='*'\"")
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-3)
typedef DPI_AWARENESS_CONTEXT (WINAPI* SetThreadDpiAwarenessContextProc)(DPI_AWARENESS_CONTEXT);
typedef LRESULT (WINAPI* GetDpiForMonitorProc)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);
static wchar_t* wszText = _wcsdup(L"1. m_pRenderTarget->DrawText(\"Hello World\");");
UINT32 cTextLength = (UINT32)wcslen(wszText);
const UINT baseDpi = 96;
UINT dpiX = baseDpi;
UINT dpiY = baseDpi;
FLOAT dpiScaleX = 1.0f;
FLOAT dpiScaleY = 1.0f;
HWND CreateMainWindow(HINSTANCE hInstance);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL CreateDirectWrite();
BOOL GetDesktopDpi();
BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct);
void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify);
void OnSize(HWND hwnd, UINT state, int cx, int cy);
void OnDpiChanged(HWND hwnd, UINT newXDpi, UINT newYDpi, RECT const &newScaledWindowRect);
void OnPaint(HWND hwnd);
void OnDestroy(HWND hwnd);
BOOL RecreateRenderTarget(const HWND hwnd, const D2D1_SIZE_U& size);
void Debug(LPCTSTR lpszFormat, ...);
INT WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ INT nCmdShow) {
// Initialize the common controls
INITCOMMONCONTROLSEX icex;
ZeroMemory(&icex, sizeof(icex));
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icex);
SetThreadDpiAwarenessContextProc SetThreadDpiAwarenessContext;
HMODULE hUser32 = GetModuleHandleW(L"user32");
if (hUser32) {
// EnableNonClientDpiScaling = (EnableNonClientDpiScalingProc)GetProcAddress(hUser32, "EnableNonClientDpiScaling");
SetThreadDpiAwarenessContext = (SetThreadDpiAwarenessContextProc)GetProcAddress(hUser32, "SetThreadDpiAwarenessContext");
if (SetThreadDpiAwarenessContext) {
DPI_AWARENESS_CONTEXT previousDpiContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}
} else {
MessageBox(NULL, "user32.dll not found.", "GetModuleHandleW Error", MB_ICONERROR);
return 1;
}
if (!CreateDirectWrite()) {
MessageBox(NULL,
"DirectWrite failed.",
"DirectWrite Error", MB_ICONERROR);
return 1;
}
HWND hwnd = CreateMainWindow(hInstance);
if (hwnd == 0) {
return 1;
}
GetDpiForMonitorProc GetDpiForMonitor;
HMODULE hShcore = LoadLibraryW(L"shcore.dll");
if (hShcore) {
GetDpiForMonitor = (GetDpiForMonitorProc)GetProcAddress(hShcore, "GetDpiForMonitor");
if (GetDpiForMonitor) {
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
dpiScaleX = (FLOAT)dpiX / baseDpi;
dpiScaleY = (FLOAT)dpiY / baseDpi;
Debug("Win32DirectWrite: GetDpiForMonitor dpiX=%d dpiY=%d dpiScaleX=%f dpiScaleY=%f", dpiX, dpiY, dpiScaleX, dpiScaleY);
}
} else {
MessageBox(NULL, "shcore.dll not found.", "LoadLibraryW Error", MB_ICONERROR);
return 1;
}
ShowWindow(hwnd, nCmdShow);
// Run the message loop.
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
HWND CreateMainWindow(HINSTANCE hInstance) {
// Register the window class.
LPCTSTR CLASS_NAME = "Window Class";
WNDCLASS wc;
ZeroMemory(&wc, sizeof(wc));
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
return CreateWindowEx(0, CLASS_NAME, "Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
320 * dpiScaleX, 300 * dpiScaleY,
//CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
};
#define HANDLE_WM_DPICHANGED(hwnd, wParam, lParam, fn) ((fn)((hwnd), LOWORD(wParam), HIWORD(wParam), *((RECT*)(lParam))), 0L)
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
HANDLE_MSG(hwnd, WM_CREATE, OnCreate);
HANDLE_MSG(hwnd, WM_COMMAND, OnCommand);
HANDLE_MSG(hwnd, WM_SIZE, OnSize);
HANDLE_MSG(hwnd, WM_DPICHANGED, OnDpiChanged);
HANDLE_MSG(hwnd, WM_PAINT, OnPaint);
HANDLE_MSG(hwnd, WM_DESTROY, OnDestroy);
case WM_ERASEBKGND:
return 1;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
static ID2D1Factory* pD2DFactory = NULL;
static IDWriteFactory* pDWriteFactory = NULL;
static IDWriteTextFormat* pTextFormat = NULL;
static ID2D1HwndRenderTarget* pRT = NULL;
static ID2D1SolidColorBrush* pBlackBrush = NULL;
enum RenderingParams {
Default = 0,
GDIClassic,
GDINatural,
Natural,
Contrast2x,
Contrast02,
GammaHalf,
Gamma2x,
GDIClassicContrast2x,
GDIClassicContrast02,
RenderingParamsCount
};
static IDWriteRenderingParams* pRenderingParams[RenderingParamsCount] = {};
template <class T> void SafeRelease(T **ppT) {
if (*ppT) {
(*ppT)->Release();
*ppT = NULL;
}
}
FLOAT ConvertPointSizeToDIP(FLOAT points) {
return points * 96.0f / 72.0f;
}
BOOL CreateDirectWrite() {
return
SUCCEEDED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
&pD2DFactory)) &&
GetDesktopDpi() &&
SUCCEEDED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&pDWriteFactory))) &&
SUCCEEDED(pDWriteFactory->CreateRenderingParams(&pRenderingParams[Default])) &&
#define CUSTOM_RENDERING_PARAMS(params, gf, ef, rm) \
SUCCEEDED(pDWriteFactory->CreateCustomRenderingParams( \
pRenderingParams[Default]->GetGamma() * gf, \
pRenderingParams[Default]->GetEnhancedContrast() * ef, \
pRenderingParams[Default]->GetClearTypeLevel(), \
pRenderingParams[Default]->GetPixelGeometry(), \
rm, &pRenderingParams[params]))
CUSTOM_RENDERING_PARAMS(GDIClassic, 1.f, 1.f, DWRITE_RENDERING_MODE_GDI_CLASSIC) &&
CUSTOM_RENDERING_PARAMS(GDINatural, 1.f, 1.f, DWRITE_RENDERING_MODE_GDI_NATURAL) &&
CUSTOM_RENDERING_PARAMS(Natural, 1.f, 1.f, DWRITE_RENDERING_MODE_NATURAL ) &&
CUSTOM_RENDERING_PARAMS(Contrast2x, 1.f, 2.f, DWRITE_RENDERING_MODE_NATURAL ) &&
CUSTOM_RENDERING_PARAMS(Contrast02, 1.f, .2f, DWRITE_RENDERING_MODE_NATURAL ) &&
CUSTOM_RENDERING_PARAMS(GammaHalf, .5f, 1.f, DWRITE_RENDERING_MODE_NATURAL ) &&
CUSTOM_RENDERING_PARAMS(Gamma2x, 2.f, 1.f, DWRITE_RENDERING_MODE_NATURAL ) &&
CUSTOM_RENDERING_PARAMS(GDIClassicContrast2x, 1.f, 2.f, DWRITE_RENDERING_MODE_GDI_CLASSIC) &&
CUSTOM_RENDERING_PARAMS(GDIClassicContrast02, 1.f, .5f, DWRITE_RENDERING_MODE_GDI_CLASSIC) &&
SUCCEEDED(pDWriteFactory->CreateTextFormat(
L"Consolas", // Font family name.
NULL, // Font collection (NULL sets it to use the system font collection).
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
ceilf(ConvertPointSizeToDIP(8)),
L"en-us",
&pTextFormat));
}
BOOL GetDesktopDpi() {
FLOAT fDpiX, fDpiY;
pD2DFactory->GetDesktopDpi(&fDpiX, &fDpiY);
dpiX = fDpiX;
dpiY = fDpiY;
dpiScaleX = fDpiX / baseDpi;
dpiScaleY = fDpiY / baseDpi;
Debug("Win32DirectWrite: GetDesktopDpi fDpiX=%f fDpiY=%f dpiScaleX=%f dpiScaleY=%f", fDpiX, fDpiY, dpiScaleX, dpiScaleY);
return TRUE;
}
BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) {
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);
if (!RecreateRenderTarget(hwnd, size)) {
MessageBox(NULL,
"OnCreate failed.",
"OnCreate Error", MB_ICONERROR);
return FALSE;
}
return TRUE;
}
void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {
Debug("Win32DirectWrite: OnCommand hwnd=0x%X id=%d hwndCtl=0x%X codeNotify=%d", hwnd, id, hwndCtl, codeNotify);
switch (id) {}
}
void OnSize(HWND hwnd, UINT state, int cx, int cy) {
Debug("Win32DirectWrite: OnSize hwnd=0x%X cx=%d cy=%d", hwnd, cx, cy);
if (state == SIZE_RESTORED || state == SIZE_MAXIMIZED) {}
D2D1_SIZE_U size = D2D1::SizeU(cx, cy);
RecreateRenderTarget(hwnd, size);
RedrawWindow(hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE);
}
void OnDpiChanged(HWND hwnd, UINT newDpiX, UINT newDpiY, const RECT& newScaledWindowRect) {
Debug("Win32DirectWrite: OnDpiChanged hwnd=0x%X newDpiX=%d newDpiY=%d", hwnd, newDpiX, newDpiY);
dpiX = newDpiX;
dpiY = newDpiY;
dpiScaleX = (FLOAT)dpiX / baseDpi;
dpiScaleY = (FLOAT)dpiY / baseDpi;
Debug("Win32DirectWrite: dpiScaleX=%f dpiScaleY=%f", dpiScaleX, dpiScaleY);
SetWindowPos(hwnd, HWND_TOP,
newScaledWindowRect.left,
newScaledWindowRect.top,
newScaledWindowRect.right - newScaledWindowRect.left,
newScaledWindowRect.bottom - newScaledWindowRect.top,
SWP_NOZORDER | SWP_NOACTIVATE); // NOTE: Will cause OnSize and RecreateRenderTarget
RedrawWindow(hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE);
}
void OnPaintHDC(HWND hwnd) {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
//FillRect(hdc, &ps.rcPaint, (HBRUSH)GetStockObject(BLACK_BRUSH));
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW+1));
EndPaint(hwnd, &ps);
}
void OnPaint(HWND hwnd) {
//Debug("Win32DirectWrite: OnPaint hwnd=0x%X", hwnd);
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_RECT_F layoutRect = D2D1::RectF(
static_cast<FLOAT>(rc.left) / dpiScaleX,
static_cast<FLOAT>(rc.top) / dpiScaleY,
static_cast<FLOAT>(rc.right - rc.left) / dpiScaleX,
static_cast<FLOAT>(rc.bottom - rc.top) / dpiScaleY
);
pRT->BeginDraw();
pRT->SetTransform(D2D1::IdentityMatrix());
pRT->Clear(D2D1::ColorF(D2D1::ColorF::White));
#define RENDER_TEXT_T(s) L##s
#define RENDER_TEXT_STR(s) RENDER_TEXT_T(#s)
#define RENDER_TEXT(index, params, ...) \
wszText[0] = RENDER_TEXT_STR(index)[0]; \
wszText[1] = index >= 10 ? RENDER_TEXT_STR(index)[1] : L'.'; \
pRT->SetTransform(D2D1::Matrix3x2F::Translation(D2D1::SizeF(10.0f, 10.0f + (index - 1) * 20.0f))); \
pRT->SetTextRenderingParams(pRenderingParams[params]); \
pRT->DrawText( \
wszText, /* The string to render. */ \
cTextLength, /* The string's length. */ \
pTextFormat, /* The text format. */ \
layoutRect, /* The region of the window where the text will be rendered. */ \
pBlackBrush, /* The brush used to draw the text. */ \
##__VA_ARGS__ \
)
RENDER_TEXT(1, Default);
RENDER_TEXT(2, GDIClassic, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_GDI_CLASSIC);
RENDER_TEXT(3, GDINatural, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_GDI_NATURAL);
RENDER_TEXT(4, GDINatural, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_NATURAL);
RENDER_TEXT(5, Contrast02, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_NATURAL);
RENDER_TEXT(6, Contrast02, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_GDI_CLASSIC);
RENDER_TEXT(7, Contrast2x, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_GDI_CLASSIC);
RENDER_TEXT(8, Gamma2x, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_GDI_CLASSIC);
RENDER_TEXT(9, GammaHalf, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_GDI_CLASSIC);
RENDER_TEXT(10, GDIClassicContrast02, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_GDI_CLASSIC);
RENDER_TEXT(11, GDIClassicContrast2x, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_GDI_CLASSIC);
pRT->EndDraw();
}
void OnDestroy(HWND hwnd) {
PostQuitMessage(0);
SafeRelease(&pBlackBrush);
for (int i = 0; i < RenderingParamsCount; ++i) {
SafeRelease(&pRenderingParams[i]);
}
SafeRelease(&pRT);
SafeRelease(&pTextFormat);
SafeRelease(&pDWriteFactory);
SafeRelease(&pD2DFactory);
}
BOOL RecreateRenderTarget(const HWND hwnd, const D2D1_SIZE_U& size) {
SafeRelease(&pBlackBrush);
SafeRelease(&pRT);
D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties = D2D1::RenderTargetProperties();
renderTargetProperties.dpiX = dpiX;
renderTargetProperties.dpiY = dpiY;
return
SUCCEEDED(pD2DFactory->CreateHwndRenderTarget(
renderTargetProperties,
D2D1::HwndRenderTargetProperties(
hwnd,
size),
&pRT)) &&
SUCCEEDED(pRT->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black),
&pBlackBrush));
}
void Debug(LPCTSTR lpszFormat, ...) {
TCHAR szBuffer[512] = "\0";
va_list args;
va_start(args, lpszFormat);
_vsnprintf(szBuffer, sizeof(szBuffer)/sizeof(*szBuffer), lpszFormat, args);
OutputDebugString(szBuffer);
va_end(args);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment