There should not be a significant difference between the two for most drawing activities, but of course I wrote a test project to compare the difference between them (well, in fact, 3 of them).
For a very large number of lines (x25000) on my machine, DrawLines () (640 ms) is about 50% faster compared to DrawLine () (420 ms). To be honest here, I also misunderstood the question for the first time and wrote my initial C # test. Performance was roughly the same between the two, which is to be expected since .NET Graphics is based on GDI +.
Just out of curiosity, I tried the usual GDI, which I expect will be faster. Using the win32 PolyLine () function (530 ms) was approximately 20% faster, with 45,000 lines. This is 116% faster than using GDI + DrawLines (). Perhaps even more overwhelming is that using win32 LineTo () instead of GDI + DrawLine () results in times of up to 125 ms. With an estimated time of 125 ms and 45,000 lines, this method is at least 800% faster. (Timer resolution and thread synchronization make it difficult to measure performance at this threshold without resorting to QueryPerformanceCounter and other higher frequency synchronization methods.)
However, I must caution you against the assumption that this is a significant bottleneck in the drawing code. Many of the performance improvements that can be made will have nothing to do with what objects need to be drawn. I would suggest that your requirements are likely to dictate that several hundred elements may be required for your control to function properly. In this case, I would recommend that you write your drawing code as simple and error-free as possible, since debugging drawing problems can be costly and time-consuming, as it improves the rest of your control or your application.
In addition, if you need to actively update thousands of items, you will see a much higher performance increase by switching to a backup buffer solution. This should also facilitate the development of management code, in addition to managing the off-screen buffer.
Here are my source code examples. Each of them handles mouse clicks to alternate between using a volume drawing and drawing detail.
GDI + hosted in the barebones MFC SDI App
This assumes that someone has already declared the GDI + headers and written the code to initialize / break GDI +.
In ChildView.h
// Attributes public: bool m_bCompositeMode; // Operations public: void RedrawScene(Graphics &g, int lineCount, int width, int height); PointF *CreatePoints(int lineCount, int width, int height); void ReportTime(Graphics &g, int lineCount, DWORD tickSpan); public: afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
In ChildView.cpp added to PreCreateWindow ()
m_bCompositeMode = false;
The remainder of ChildView.cpp, including OnPaint () and message map changes.
BEGIN_MESSAGE_MAP(CChildView, CWnd) ON_WM_PAINT() ON_WM_LBUTTONUP() END_MESSAGE_MAP() void CChildView::OnPaint() { CPaintDC dc(this); // device context for painting RECT rcClient; ::GetClientRect(this->GetSafeHwnd(), &rcClient); Graphics g(dc.GetSafeHdc()); g.Clear(Color(0, 0, 0)); RedrawScene(g, 25000, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); } void CChildView::RedrawScene(Graphics &g, int lineCount, int width, int height) { DWORD tickStart = 0; DWORD tickEnd = 0; Pen p(Color(0, 0, 0x7F)); PointF *pts = CreatePoints(lineCount, width, height); tickStart = GetTickCount(); if (m_bCompositeMode) { g.DrawLines(&p, pts, lineCount); } else { int i = 0; int imax = lineCount - 1; for (i = 0; i < imax; i++) { g.DrawLine(&p, pts[i], pts[i + 1]); } } tickEnd = GetTickCount(); delete[] pts; ReportTime(g, lineCount, tickEnd - tickStart); } void CChildView::ReportTime(Graphics &g, int lineCount, DWORD tickSpan) { CString strDisp; if(m_bCompositeMode) { strDisp.Format(_T("Graphics::DrawLines(Pen *, PointF *, INT) x%d took %dms"), lineCount, tickSpan); } else { strDisp.Format(_T("Graphics::DrawLine(Pen *, PointF, PointF) x%d took %dms"), lineCount, tickSpan); } // Note: sloppy, but simple. Font font(L"Arial", 14.0f); PointF ptOrigin(0.0f, 0.0f); SolidBrush br(Color(255, 255, 255)); Status s = g.DrawString(strDisp, -1, &font, ptOrigin, &br); } PointF* CChildView::CreatePoints(int lineCount, int width, int height) { if(lineCount <= 0) { PointF *ptEmpty = new PointF[2]; ptEmpty[0].X = 0; ptEmpty[0].Y = 0; ptEmpty[1].X = 0; ptEmpty[1].Y = 0; return ptEmpty; } PointF *pts = new PointF[lineCount + 1]; int i = 1; while(i < lineCount) { pts[i].X = (float)(rand() % width); pts[i].Y = (float)(rand() % height); i++; } return pts; } void CChildView::OnLButtonUp(UINT nFlags, CPoint point) { m_bCompositeMode = !m_bCompositeMode; this->Invalidate(); CWnd::OnLButtonUp(nFlags, point); }
C # .NET hosted in WinForms App database, with default class Form1
Set the default size for the form to be the size of the MFC version if you are comparing the two. You can also add a resize handler.
public Form1() { InitializeComponent(); bCompositeMode = false; } bool bCompositeMode; private void Form1_Paint(object sender, PaintEventArgs e) { e.Graphics.Clear(Color.Black); RedrawScene(e.Graphics, 25000, this.ClientRectangle.Width, this.ClientRectangle.Height); } private void RedrawScene(Graphics g, int lineCount, int width, int height) { DateTime dtStart = DateTime.MinValue; DateTime dtEnd = DateTime.MinValue; using (Pen p = new Pen(Color.Navy)) { Point[] pts = CreatePoints(lineCount, width, height); dtStart = DateTime.Now; if (bCompositeMode) { g.DrawLines(p, pts); } else { int i = 0; int imax = pts.Length - 1; for (i = 0; i < imax; i++) { g.DrawLine(p, pts[i], pts[i + 1]); } } dtEnd = DateTime.Now; } ReportTime(g, lineCount, dtEnd - dtStart); } private void ReportTime(Graphics g, int lineCount, TimeSpan ts) { string strDisp = null; if (bCompositeMode) { strDisp = string.Format("DrawLines(Pen, Point[]) x{0} took {1}ms", lineCount, ts.Milliseconds); } else { strDisp = string.Format("DrawLine(Pen, Point, Point) x{0} took {1}ms", lineCount, ts.Milliseconds); }
Regular GDI hosted in the barebone MFC SDI App
In ChildView.h
// Attributes public: bool m_bCompositeMode; // Operations public: void RedrawScene(HDC hdc, int lineCount, int width, int height); POINT *CreatePoints(int lineCount, int width, int height); void ReportTime(HDC hdc, int lineCount, DWORD tickSpan); public: afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
In ChildView.cpp
Update PreCreateWindow () in the same way as in the GDI + example.
BEGIN_MESSAGE_MAP(CChildView, CWnd) ON_WM_PAINT() ON_WM_LBUTTONUP() END_MESSAGE_MAP() void CChildView::OnPaint() { CPaintDC dc(this); // device context for painting HDC hdc = dc.GetSafeHdc(); HBRUSH brClear = (HBRUSH)::GetStockObject(BLACK_BRUSH); RECT rcClient; ::GetClientRect(this->m_hWnd, &rcClient); ::FillRect(hdc, &rcClient, brClear); ::DeleteObject(brClear); RedrawScene(hdc, 45000, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); } void CChildView::RedrawScene(HDC hdc, int lineCount, int width, int height) { DWORD tickStart = 0; DWORD tickEnd = 0; HPEN p = ::CreatePen(PS_SOLID, 1, RGB(0, 0, 0x7F)); POINT *pts = CreatePoints(lineCount, width, height); HGDIOBJ prevPen = SelectObject(hdc, p); tickStart = GetTickCount(); if(m_bCompositeMode) { ::Polyline(hdc, pts, lineCount); } else { ::MoveToEx(hdc, pts[0].x, pts[0].y, &(pts[0])); int i = 0; int imax = lineCount; for(i = 1; i < imax; i++) { ::LineTo(hdc, pts[i].x, pts[i].y); } } tickEnd = GetTickCount(); ::SelectObject(hdc, prevPen); delete pts; ::DeleteObject(p); ReportTime(hdc, lineCount, tickEnd - tickStart); } POINT *CChildView::CreatePoints(int lineCount, int width, int height) { if(lineCount <= 0) { POINT *ptEmpty = new POINT[2]; memset(&ptEmpty, 0, sizeof(POINT) * 2); return ptEmpty; } POINT *pts = new POINT[lineCount + 1]; int i = 1; while(i < lineCount) { pts[i].x = rand() % width; pts[i].y = rand() % height; i++; } return pts; } void CChildView::ReportTime(HDC hdc, int lineCount, DWORD tickSpan) { CString strDisp; if(m_bCompositeMode) { strDisp.Format(_T("PolyLine(HDC, POINT *, int) x%d took %dms"), lineCount, tickSpan); } else { strDisp.Format(_T("LineTo(HDC, HPEN, int, int) x%d took %dms"), lineCount, tickSpan); } HFONT font = (HFONT)::GetStockObject(SYSTEM_FONT); HFONT fontPrev = (HFONT)::SelectObject(hdc, font); RECT rcClient; ::GetClientRect(this->m_hWnd, &rcClient); ::ExtTextOut(hdc, 0, 0, ETO_CLIPPED, &rcClient, strDisp.GetString(), strDisp.GetLength(), NULL); ::SelectObject(hdc, fontPrev); ::DeleteObject(font); } void CChildView::OnLButtonUp(UINT nFlags, CPoint point) { m_bCompositeMode = !m_bCompositeMode; this->Invalidate(); CWnd::OnLButtonUp(nFlags, point); }