// cl /std:c++20 b.cpp user32.lib gdi32.lib d2d1.lib comctl32.lib uxtheme.lib dwmapi.lib ole32.lib gdiplus.lib dwrite.lib #ifndef UNICODE #define UNICODE #endif #ifdef __MINGW32__ #define HAVE_CAIRO 1 #else #define HAVE_CAIRO 0 #endif #include #include #include //#include #include #include #include using namespace Gdiplus; #pragma comment (lib,"Gdiplus.lib") // C RunTime Header Files: #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_CAIRO #include #include #endif #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif int bench_count = 1; enum { WIDGET_RENDERER = 123, WIDGET_CLICKME, WIDGET_DOBENCH, WIDGET_LIMIT, }; #define RENDER_BACKENDS \ Q(GDI, "GDI") \ Q(GDIP, "GDI+") \ Q(D2D, "Direct2D") \ Q(CAIRO, "Cairo") #define Q(x,y) RENDER_##x, enum { RENDER_BACKENDS RENDER_LIMIT }; #undef Q #define Q(x,y) y, const char *render_name [] = { RENDER_BACKENDS 0 }; #undef Q #define CHECK_ZERO(x) do{ \ if(0 != (x)) { \ fprintf(stderr, "%s:%d: Didn't happen for us: %s\n", \ __FILE__, __LINE__, #x); \ exit(1); \ }}while(0) #define CHECK_NONZERO(x) do{ \ if(0 == (x)) { \ fprintf(stderr, "%s:%d: Didn't happen for us: %s\n", \ __FILE__, __LINE__, #x); \ exit(1); \ }}while(0) #if 0 /* This has no effect, it seems */ #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") #endif #pragma comment(linker,"/manifestdependency:\"type='win32' "\ "name='Microsoft.Windows.Common-Controls' "\ "version='6.0.0.0' "\ "processorArchitecture='*' "\ "publicKeyToken='6595b64144ccf1df' "\ "language='*' "\ "\"") #include "msgname.c" double now (void) { FILETIME ti; GetSystemTimeAsFileTime(&ti); return 100e-9 * ((4294967296.0 * ti.dwHighDateTime + ti.dwLowDateTime)); } int x = 42; ID2D1Factory* pD2DFactory = NULL; LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT DrawingArea_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); HWND hwndEdit, da_wnd; HWND hWndComboBox; int say (const char *fmt, ...) { static char buf [1<<16]; // enough! int r; va_list ap; va_start(ap, fmt); r = vsnprintf (buf, sizeof(buf), fmt, ap); int index = GetWindowTextLength (hwndEdit); SendMessage(hwndEdit, EM_SETSEL, (WPARAM)index, (LPARAM)index); // set selection - end of text SendMessageA(hwndEdit, EM_REPLACESEL, 0, (LPARAM)buf); // append! return r; } HFONT guiFont; HWND make_button (HWND parent, int x, int y, LPWSTR label, HMENU menu) { HWND button; CHECK_NONZERO(button = CreateWindow(L"BUTTON", // Predefined class; Unicode assumed label, // Button text WS_TABSTOP | WS_VISIBLE | WS_CHILD /* | BS_DEFPUSHBUTTON */, // Styles x, // x position y, // y position 100, // Button width 100, // Button height parent, // Parent window (HMENU)menu, 0, NULL)); // Pointer not needed. SendMessage(button, WM_SETFONT, (LPARAM)guiFont, true); { SIZE sz = { -1, -1 }; int r = SendMessage(button, BCM_GETIDEALSIZE, 0, (LPARAM)&sz); printf("%d,%d,%d\n", r, sz.cx, sz.cy); if(sz.cx < 60) sz.cx = 60; if(sz.cy < 24) sz.cy = 24; //sz.cx += 6; sz.cy += 6; SetWindowPos(button, 0, x, y, sz.cx, sz.cy, 0); } ShowWindow(button, SW_SHOW); return button; } //int wWinMain(HINSTANCE hInstance, HINSTANCE blah, PWSTR pCmdLine, int nCmdShow) int main (void) { GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; InitCommonControls (); CHECK_ZERO(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory)); CHECK_NONZERO(Ok == GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL)); // Register the window class. const wchar_t CLASS_NAME[] = L"FOO"; { WNDCLASSEX wc = { .cbSize = sizeof (wc), .style = CS_HREDRAW | CS_VREDRAW, .lpfnWndProc = WindowProc, .cbClsExtra = 0, .cbWndExtra = 0, .hInstance = GetModuleHandle(0), .hIcon = 0, .hCursor = LoadCursor(0, IDC_ARROW), .hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH), .lpszMenuName = 0, .lpszClassName = CLASS_NAME, .hIconSm = 0 }; RegisterClassEx(&wc); } { WNDCLASSEX wc = { .cbSize = sizeof (wc), .style = CS_HREDRAW | CS_VREDRAW, .lpfnWndProc = DrawingArea_Proc, .cbClsExtra = 0, .cbWndExtra = 0, .hInstance = GetModuleHandle(0), .hIcon = 0, .hCursor = LoadCursor(0, IDC_ARROW), .hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH), .lpszMenuName = 0, .lpszClassName = L"DA", .hIconSm = 0 }; RegisterClassEx(&wc); } // Create the window. HWND hwnd; HWND hwndButton; CHECK_NONZERO (hwnd = CreateWindowEx(0, // Optional window styles. CLASS_NAME, // Window class L"Hello there!", // Window text WS_EX_COMPOSITED | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, // Window style // Size and position CW_USEDEFAULT, CW_USEDEFAULT, 1200, 800, //CW_USEDEFAULT, CW_USEDEFAULT, NULL, // Parent window (HMENU)0, // Menu GetModuleHandle(0), // Instance handle NULL)); // Additional application data CHECK_NONZERO (da_wnd = CreateWindowEx(0, // Optional window styles. L"DA", // Window class L"", // Window text WS_CHILD , // Window style // Size and position 20, 50, 1180, 600, //CW_USEDEFAULT, CW_USEDEFAULT, hwnd, // Parent window (HMENU)0, // Menu GetModuleHandle(0), // Instance handle NULL)); // Additional application data make_button(da_wnd, 100, 200, (LPWSTR)L"Click me if you can", (HMENU)WIDGET_CLICKME); ShowWindow(da_wnd, SW_SHOW); { /* This has no effect. */ BOOL value = TRUE; DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); } hwndEdit = CreateWindowEx( 0, L"EDIT", // predefined class NULL, // no window title WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL, 10, 670, 1200, 300, hwnd, // parent window (HMENU) 4711, // edit control ID (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE), NULL); // pointer not needed SendMessage(hwndEdit, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), 1); // Add text to the window. SendMessage(hwndEdit, WM_SETTEXT, 0, (LPARAM)L"Hello\n"); SendMessage(hwndEdit, EM_SETREADONLY, 1, 0); { int index = GetWindowTextLength (hwndEdit); //SetFocus (hwndEdit); // set focus SendMessage(hwndEdit, EM_SETSEL, (WPARAM)index, (LPARAM)index); // set selection - end of text SendMessage(hwndEdit, EM_REPLACESEL, 0, (LPARAM)L"there"); // append! } NONCLIENTMETRICS metrics = {}; metrics.cbSize = sizeof(metrics); CHECK_NONZERO(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0)); guiFont = CreateFontIndirect(&metrics.lfCaptionFont); hwndButton = make_button(hwnd, 220, 10, (LPWSTR)L"Run Benchmark", (HMENU)WIDGET_DOBENCH); #if 0 CHECK_NONZERO(hwndButton = CreateWindow(L"BUTTON", // Predefined class; Unicode assumed L"Run Benchmark", // Button text WS_TABSTOP | WS_VISIBLE | WS_CHILD /* | BS_DEFPUSHBUTTON */, // Styles 220, // x position 10, // y position 100, // Button width 100, // Button height hwnd, // Parent window (HMENU)WIDGET_DOBENCH, 0, NULL)); // Pointer not needed. SendMessage(hwndButton, WM_SETFONT, (LPARAM)guiFont, true); { SIZE sz = { -1, -1 }; int r = SendMessage(hwndButton, BCM_GETIDEALSIZE, 0, (LPARAM)&sz); printf("%d,%d,%d\n", r, sz.cx, sz.cy); if(sz.cx < 60) sz.cx = 60; if(sz.cy < 24) sz.cy = 24; //sz.cx += 6; sz.cy += 6; SetWindowPos(hwndButton, 0, 220, 10, sz.cx, sz.cy, 0); } #endif // Create the Combobox // // Uses the CreateWindow function to create a child window of // the application window. The WC_COMBOBOX window style specifies // that it is a combobox. int xpos = 10; // Horizontal position of the window. int ypos = 10; // Vertical position of the window. int nwidth = 200; // Width of the window int nheight = 200; // Height of the window HWND hwndParent = hwnd; // Handle to the parent window hWndComboBox = CreateWindow(WC_COMBOBOX, TEXT(""), CBS_DROPDOWNLIST /* | CBS_HASSTRINGS */ | WS_CHILD | WS_OVERLAPPED | 0*WS_VISIBLE, xpos, ypos, nwidth, nheight, hwndParent, (HMENU)WIDGET_RENDERER, 0, NULL); SendMessage(hWndComboBox, WM_SETFONT, (LPARAM)guiFont, true); //SetWindowTheme(hWndComboBox, L"Explorer", NULL); { for (int i = 0; i < RENDER_LIMIT; i++) SendMessageA(hWndComboBox, (UINT)CB_ADDSTRING, (WPARAM) 0, (LPARAM) render_name[i]); } { int r; r = SendMessage(hWndComboBox, CB_GETDROPPEDWIDTH, 0, 0); printf("CB_GETDROPPEDWIDTH -> %d\n", r); r = SendMessage(hWndComboBox, CB_GETITEMHEIGHT, 0, 0); printf("CB_GETITEMHEIGHT -> %d\n", r); } ShowWindow(hWndComboBox, SW_SHOW); ShowWindow(hwnd, SW_SHOW); // Run the message loop. MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } void set_window_bounds (HWND wnd, int x1, int y1, int x2, int y2) { SetWindowPos(wnd, 0, x1, y1, x2 - x1, y2 - y1, 0); } LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if(0) { const char *nam = msg_name(uMsg); if (nam) printf("%s ", nam); else printf("0x%04x ", (unsigned)uMsg); printf("0x%04llx 0x%08llx\n", (unsigned long long)wParam, (unsigned long long)lParam); } switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); return 0; case WM_SIZE: { RECT rc; GetClientRect(hwnd, &rc); int y = rc.top; int h; h = 40; y+=h; h = (rc.bottom - rc.top) - 300; set_window_bounds(da_wnd, rc.left + 5, y, rc.right - 5, y + h); y += h; y += 5; set_window_bounds(hwndEdit, rc.left + 5, y, rc.right - 5, rc.bottom - 5); } return 0; case WM_COMMAND: if ((LOWORD(wParam) == WIDGET_RENDERER) && HIWORD(wParam) == CBN_SELCHANGE) { printf ("**** Selection change\n"); InvalidateRgn(da_wnd, 0, 0); return 0; } if (LOWORD(wParam) == WIDGET_DOBENCH && HIWORD(wParam) == 0) { bench_count = 100; InvalidateRgn(da_wnd, 0, 0); return 0; } else if(0) { printf("WM_COMMAND %llx %llx\n", (unsigned long long)wParam, (unsigned long long)lParam); if(wParam == 0xdead) { bench_count = 100; InvalidateRgn(da_wnd, 0, 0); return 0; } } } return DefWindowProc(hwnd, uMsg, wParam, lParam); } void redraw_d2d (HWND hwnd, RECT rc, double *ti_f, int *n_f) { int n = 0; double ti; HRESULT hr; // Create a Direct2D render target ID2D1HwndRenderTarget *pRT = NULL; ID2D1SolidColorBrush *pBlackBrush = NULL; ID2D1SolidColorBrush *pRedBrush = NULL; ID2D1SolidColorBrush *pGreenBrush = NULL; ID2D1SolidColorBrush *pBlueBrush = NULL; ID2D1SolidColorBrush *pWhiteBrush = NULL; CHECK_ZERO(hr = pD2DFactory->CreateHwndRenderTarget (D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties (hwnd, D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top)), &pRT)); pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &pBlackBrush); pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red), &pRedBrush); pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Green), &pGreenBrush); pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Blue), &pBlueBrush); pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), &pWhiteBrush); pRT->BeginDraw(); pRT->FillRectangle(D2D1::RectF(0,0,1e6,1e6), pWhiteBrush); { // what? float dpiScaleX_ = 1; float dpiScaleY_ = 1; ID2D1Factory* pD2DFactory_; CHECK_ZERO(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory_)); IDWriteFactory* pDWriteFactory_; CHECK_ZERO(DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&pDWriteFactory_))); //const wchar_t *wszText_ = L"Hello World using Ɵ ā†’ā† DirectWrite!"; wchar_t wszText_[1000]; int p; p = 0; const wchar_t *s; for (s = // L"(defun ncons (x)\n"" (cons x nil))\n"; L"Zoo Aƅ Q"; //L"am"; *s; s++) { wszText_[p++] = *s; } #if 0 wszText_[p++] = (wchar_t)0xD83D; wszText_[p++] = (wchar_t)0xDE00; #endif wszText_[p++] = (wchar_t)0; UINT32 cTextLength_ = (UINT32) wcslen(wszText_); IDWriteTextFormat* pTextFormat_; CHECK_ZERO(pDWriteFactory_->CreateTextFormat (//L"Gabriola", // Font family name. L"Cascadia Code", // //L"Zapf Dingbats", //L"Times New Roman", NULL, // Font collection (NULL sets it to use the system font collection). DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_ITALIC, //NORMAL, DWRITE_FONT_STRETCH_NORMAL, 4*14.0f, L"en-us", &pTextFormat_)); CHECK_ZERO(pTextFormat_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING)); CHECK_ZERO(pTextFormat_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR)); RECT rc2; GetClientRect(hwnd, &rc2); // Now this is beautiful. This text layout object assumes that we // render text inside some box, yet we can't get metrics for that // box before we render it. And those single floats they use don't // have enough room for any significant bits left when we just say // that we use a huge box. IDWriteTextLayout *pTextLayout_; CHECK_ZERO(pDWriteFactory_->CreateTextLayout (wszText_, // The string to be laid out and formatted. cTextLength_, // The length of the string. pTextFormat_, // The text format to apply to the string (contains font information, etc). 1e6, // The width of the layout box. 0, // The height of the layout box. &pTextLayout_ // The IDWriteTextLayout interface pointer. )); printf("%d\n",(int)sizeof(FLOAT)); // We cannot turn off trimming. { struct DWRITE_TRIMMING q = {.granularity = DWRITE_TRIMMING_GRANULARITY_NONE }; CHECK_ZERO(pTextLayout_->SetTrimming (&q, NULL)); } DWRITE_TEXT_METRICS text_metrics; DWRITE_LINE_METRICS line_metrics[100]; DWRITE_OVERHANG_METRICS overhang_metrics = { 0 }; UINT32 nlines; CHECK_ZERO(pTextLayout_->GetMetrics(&text_metrics)); CHECK_ZERO(pTextLayout_->GetLineMetrics(line_metrics, sizeof(line_metrics)/sizeof(line_metrics[0]), &nlines)); CHECK_ZERO(pTextLayout_->GetOverhangMetrics(&overhang_metrics)); #if 0 struct DWRITE_TEXT_METRICS { FLOAT left; FLOAT top; FLOAT width; FLOAT widthIncludingTrailingWhitespace; FLOAT height; FLOAT layoutWidth; FLOAT layoutHeight; UINT32 maxBidiReorderingDepth; UINT32 lineCount; }; #endif #if 0 struct DWRITE_LINE_METRICS { UINT32 length; UINT32 trailingWhitespaceLength; UINT32 newlineLength; FLOAT height; FLOAT baseline; BOOL isTrimmed; }; #endif #if 0 struct DWRITE_OVERHANG_METRICS { FLOAT left; FLOAT top; FLOAT right; FLOAT bottom; }; #endif #if 0 pTextLayout_->Draw( NULL, pTextRenderer_, // Custom text renderer. 100, 100 ); #endif ti = now(); n = 0; // As expected this is kinda slow. We only get like 50k/sec. Why is // text always so slow? And then we also need to measure it. Duh! // So maybe for ASCII we need a fast path somehow? int i; i = bench_count; //pRT->SetTransform(D2D1::Matrix3x2F::Scale(D2D1::Size(2, 2), D2D1::Point2F(0, 0))); while(i--) { n++; D2D1_SIZE_U size = D2D1::SizeU(rc2.right - rc2.left, rc2.bottom - rc2.top); #if 0 D2D1_RECT_F layoutRect = D2D1::RectF( static_cast(rc.left) / dpiScaleX_, static_cast(rc.top) / dpiScaleY_, static_cast(rc.right - rc.left) / dpiScaleX_, static_cast(rc.bottom - rc.top) / dpiScaleY_ ); #endif D2D1_RECT_F layoutRect = D2D1::RectF(0,0,1000,1000); if(i==0)pRT->FillRectangle(D2D1::RectF(0,0,1e6,1e6), pWhiteBrush); if(0) 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. ); { float y = 100 + line_metrics[0].baseline; printf("baseline = %g\n", line_metrics[0].baseline); // typegraphic bounds float tx1 = 100 + text_metrics.left; float ty1 = 100 + text_metrics.top - line_metrics[0].baseline; float tx2 = tx1 + text_metrics.width; float ty2 = ty1 + text_metrics.height; printf ("text metrics = (left = %g top = %g width = %g height = %g)\n", text_metrics.left, text_metrics.top, text_metrics.width, text_metrics.height); // graphics bounds float gx1 = 100 + text_metrics.left - overhang_metrics.left; float gy1 = 100 + text_metrics.top - overhang_metrics.top - line_metrics[0].baseline; float gx2 = gx1 + text_metrics.layoutWidth + overhang_metrics.right; float gy2 = gy1 + text_metrics.layoutHeight + overhang_metrics.bottom; printf("graphics bounds: %g %g %g %g\n", gx1, gy1, gx2, gy2); pRT->DrawLine(D2D1::Point2F(50,100), D2D1::Point2F(300,100), pRedBrush, 1); pRT->DrawRectangle(D2D1::RectF(tx1, ty1, tx2, ty2), pGreenBrush); pRT->DrawRectangle(D2D1::RectF(gx1, gy1, gx2, gy2), pBlueBrush); #if 0 pRT->DrawLine(D2D1::Point2F(100 + text_metrics.left, y - 20), D2D1::Point2F(100 + text_metrics.left, y + 20), pGreenBrush, 1); pRT->DrawLine(D2D1::Point2F(100 + text_metrics.left + text_metrics.width, y - 20), D2D1::Point2F(100 + text_metrics.left + text_metrics.width, y + 20), pGreenBrush, 1); #endif } pRT->DrawTextLayout(D2D1::Point2F(100,100 - line_metrics[0].baseline), pTextLayout_, pBlackBrush, D2D1_DRAW_TEXT_OPTIONS_NONE); } //goto done; } for(int i = 0; i < bench_count; i++) { int x1, x2; for (x1 = 0; x1 < 1000; x1 += 50) { for (x2 = 0; x2 < 1000; x2 += 50) { pRT->DrawLine(D2D1::Point2F(x1,100), D2D1::Point2F(x2,rc.bottom-20), pBlackBrush, 1); n++; } } } done: CHECK_ZERO(pRT->EndDraw()); *n_f = n; *ti_f = ti; } LRESULT CALLBACK DrawingArea_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); return 0; case WM_PAINT: { int r = SendMessage(hWndComboBox, (UINT)CB_GETCURSEL, (WPARAM) 0, (LPARAM) 0); //printf("r = %d\n", r); RECT rc; GetClientRect(hwnd, &rc); PAINTSTRUCT ps; HDC hdc; int n = 0; double ti = now(); hdc = BeginPaint(hwnd, &ps); if (r == RENDER_GDI) { HDC hdcMem = CreateCompatibleDC(hdc); const int nMemDC = SaveDC(hdcMem); HBITMAP hBitmap = CreateCompatibleBitmap(hdc, rc.right - rc.left, rc.bottom - rc.top); SelectObject(hdcMem, hBitmap); FillRect(hdcMem, &ps.rcPaint, (HBRUSH)GetSysColorBrush(COLOR_WINDOWTEXT)); //COLOR_WINDOWTEXT n = 0; ti = now(); for(int i = 0; i < bench_count; i++) { int x1, x2; for (x1 = 0; x1 < 1000; x1 += 50) { for (x2 = 0; x2 < 1000; x2 += 50) { MoveToEx(hdcMem, x1, 100, 0); LineTo(hdcMem, x2, 400); n++; } } } BitBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, hdcMem, rc.left, rc.top, SRCCOPY); RestoreDC(hdcMem, nMemDC); DeleteObject(hBitmap); } if (r == RENDER_D2D) { redraw_d2d(hwnd, rc, &ti, &n); } #if 0 if (r == RENDER_GDIP) { Graphics graphics(hdc); Pen pen(Color(255, 0, 0, 0)); n = 0; for(int i = 0; i < bench_count; i++) { int x1, x2; for (x1 = 0; x1 < 1000; x1 += 50) { for (x2 = 0; x2 < 1000; x2 += 50) { graphics.DrawLine(&pen, x1, 100, x2, rc.bottom - 20); n++; } } } } #endif if (r == RENDER_GDIP) { HDC hdcMem = CreateCompatibleDC(hdc); const int nMemDC = SaveDC(hdcMem); HBITMAP hBitmap = CreateCompatibleBitmap(hdc, rc.right - rc.left, rc.bottom - rc.top); SelectObject(hdcMem, hBitmap); Graphics graphics(hdcMem); graphics.SetSmoothingMode(SmoothingModeAntiAlias); Pen pen(Color(255, 0, 0, 0)); SolidBrush bg(Color(255, 255,255,255)); graphics.FillRectangle(&bg, 0, 0, 100000, 100000); n = 0; ti = now(); for(int i = 0; i < bench_count; i++) { int x1, x2; for (x1 = 0; x1 < 1000; x1 += 50) { for (x2 = 0; x2 < 1000; x2 += 50) { graphics.DrawLine(&pen, x1, 100, x2, rc.bottom - 20); n++; } } } BitBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, hdcMem, rc.left, rc.top, SRCCOPY); RestoreDC(hdcMem, nMemDC); DeleteObject(hBitmap); } #if HAVE_CAIRO if (0 && r == RENDER_CAIRO) { cairo_surface_t *surface = cairo_win32_surface_create(hdc); cairo_t *cr = cairo_create(surface); cairo_set_line_width( cr, 1); cairo_set_source_rgba( cr, 0, 0, 0, 1); n = 0; ti = now(); for(int i = 0; i < bench_count; i++) { int x1, x2; for (x1 = 0; x1 < 1000; x1 += 50) { for (x2 = 0; x2 < 1000; x2 += 50) { cairo_move_to (cr, x1, 100); cairo_line_to (cr, x2, rc.bottom - 20); cairo_stroke (cr); n++; } } } cairo_destroy(cr); cairo_surface_destroy(surface); } if (r == RENDER_CAIRO) { HDC hdcMem = CreateCompatibleDC(hdc); const int nMemDC = SaveDC(hdcMem); HBITMAP hBitmap = CreateCompatibleBitmap(hdc, rc.right - rc.left, rc.bottom - rc.top); SelectObject(hdcMem, hBitmap); FillRect(hdcMem, &ps.rcPaint, (HBRUSH)GetStockObject(WHITE_BRUSH)); cairo_surface_t *surface = cairo_win32_surface_create(hdcMem); cairo_t *cr = cairo_create(surface); cairo_set_line_width( cr, 1); cairo_set_source_rgba( cr, 0, 0, 0, 1); n = 0; ti = now(); for(int i = 0; i < bench_count; i++) { int x1, x2; for (x1 = 0; x1 < 1000; x1 += 50) { for (x2 = 0; x2 < 1000; x2 += 50) { cairo_move_to (cr, x1, 100); cairo_line_to (cr, x2, rc.bottom - 20); cairo_stroke (cr); n++; } } } cairo_destroy(cr); cairo_surface_destroy(surface); BitBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, hdcMem, rc.left, rc.top, SRCCOPY); RestoreDC(hdcMem, nMemDC); DeleteObject(hBitmap); } #endif EndPaint(hwnd, &ps); if (bench_count > 1) { ti = now() - ti; if (ti == 0) ti = 1e-9; say("%s: %.2fms %d lines, %g lines/sec\r\n", (r == -1) ? "none" : render_name[r], ti*1000, n, (double)n/(double)ti); } bench_count = 1; } return 0; case WM_COMMAND: { printf("*** Huh? I'm the drawing area. WM_COMMAND %llx %llx\n", (unsigned long long)wParam, (unsigned long long)lParam); if(wParam == 0xdead) { InvalidateRgn(hwnd, 0, 0); return 0; } } } return DefWindowProc(hwnd, uMsg, wParam, lParam); } extern "C" void d2d_draw_line (ID2D1HwndRenderTarget *target, double x1, double y1, double x2, double y2, ID2D1SolidColorBrush *brush, double width); void d2d_draw_line (ID2D1HwndRenderTarget *target, double x1, double y1, double x2, double y2, ID2D1SolidColorBrush *brush, double width) { target->DrawLine(D2D1::Point2F(x1,y1), D2D1::Point2F(x2,y2), brush, width); } extern "C" void d2d_draw_line_f (ID2D1HwndRenderTarget *target, float x1, float y1, float x2, float y2, ID2D1SolidColorBrush *brush, float width); void d2d_draw_line_f (ID2D1HwndRenderTarget *target, float x1, float y1, float x2, float y2, ID2D1SolidColorBrush *brush, float width) { target->DrawLine(D2D1::Point2F(x1,y1), D2D1::Point2F(x2,y2), brush, width); } // GDI: 600,000 lines/sec // GDI+: // g++ -o foo.exe b.cpp -luser32 -lgdi32 -ld2d1 -lcomctl32 -luxtheme -ldwmapi -lole32 -lgdiplus -lcairo // 2cd90694-12e2-11dc-9fed-001143a055f9 // cl /std:c++20 b.cpp user32.lib gdi32.lib d2d1.lib comctl32.lib uxtheme.lib dwmapi.lib ole32.lib gdiplus.lib dwrite.lib