|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionGDI+ gives developers many advanced graphic functions and looks more friendly than GDI. I wanted to answer the question 'how fast is GDI+?' and 'what can we do to get smooth and fast redrawing?'. To estimate drawing speed I checked the "Show window contents while dragging" in the Control Panel - Display Properties dialog, Effects tag. I then ran the program which looks like this:
I made two tests
Redrawing should be both smooth and fast. For the demo project I used Windows Template Library (WTL) which is available in the Microsoft Platform SDK. WTL is my preferable framework for writing small Windows programs without MFC. To compile the demo project add the WTL Include directory to the list of Visual C++ Include directories. For GDI+ use the Microsoft SDK Include\Prerelease and Lib\Prerelease directories. The Drawing code is very simple - horizontal and vertical lines on white background. The Window doesn't erase the background itself and the client area is filled using GDI+. To get the exact time of redrawing I use my class I made four demo projects - from step 1 (drawing directly to window) to step 4 (using cashed bitmap). All projects are created using the WTL Application Wizard, SDI application without View class. For each step I write the execution time. It depends on many factors, such as operating system, computer and graphic card speed, program configuration, window size etc., but I believe the proportions between execution speed on various steps will be the same in any case. Code fragments and execution timeStep 1. Drawing directly to windowclass CMainFrame : public CFrameWindowImpl<CMainFrame>,Execution time in Output window:
Step 2 - Using off-screen graphics LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SHOW_TIME_START(t, _T("OnPaint"));
CPaintDC dc(m_hWnd);
RECT rect;
GetClientRect(&rect);
int nWidth = rect.right - rect.left + 1;
int nHeight = rect.bottom - rect.top + 1;
Graphics graphics(dc.m_hDC);
SHOW_TIME_START(t1, _T("Prepare off-screen bitmap"));
// memory bitmap
Bitmap* pMemBitmap = new Bitmap(nWidth, nHeight);
// offscreen graphics
Graphics* pMemGraphics = Graphics::FromImage(pMemBitmap);
// draw to memory bitmap
Draw(pMemGraphics, nWidth, nHeight);
SHOW_TIME_END(t1);
// draw from memory bitmap to window
graphics.DrawImage(pMemBitmap, 0, 0);
delete pMemGraphics;
delete pMemBitmap;
SHOW_TIME_END(t);
return 0;
}
// draw lines to Graphics
void Draw(Graphics* pGraphics, int nWidth, int nHeight)
{
// fill background
SolidBrush solidBrush(Color(255, 255, 255));
pGraphics->FillRectangle(&solidBrush, 0, 0, nWidth, nHeight);
Pen pen(Color(255, 0, 0, 255));
int nStep, i;
int nLines = 20;
// draw horizontal lines
nStep = nHeight / nLines;
for ( i = 0; i < nLines; i++ )
{
pGraphics->DrawLine(&pen, 0, nStep*i, nWidth, nStep*i);
}
// draw vertical lines
nStep = nWidth / nLines;
for ( i = 0; i < nLines; i++ )
{
pGraphics->DrawLine(&pen, nStep*i, 0, nStep*i, nHeight);
}
}
Execution time in the Output window:
Preparing the off-screen bitmap is part of the drawing. That means window redrawing takes 130 ms, 40 ms of them is preparing the off-screen bitmap. Step 3 - Using off-screen graphics as class memberIn this step there are two redrawing times. Full redrawing is done when the window is resizing. Redrawing without resizing takes less time, because the off-screen graphics with the required size is ready for use. CMainFrame()
{
m_pMemBitmap = NULL;
m_pMemGraphics = NULL;
}
// clean-up operations
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// release off-screen bitmap and graphics
if ( m_pMemGraphics )
delete m_pMemGraphics;
if ( m_pMemBitmap )
delete m_pMemBitmap;
// ...
}
// cause to redraw all window client area while resizing
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
Invalidate();
SetDirty();
return 0;
}
// cause to create offscreen graphics when window will be redrawn
void SetDirty()
{
if ( m_pMemGraphics )
{
delete m_pMemGraphics;
m_pMemGraphics = NULL;
}
if ( m_pMemBitmap )
{
delete m_pMemBitmap;
m_pMemBitmap = NULL;
}
}
// redraw window
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SHOW_TIME_START(t, _T("OnPaint"));
CPaintDC dc(m_hWnd);
RECT rect;
GetClientRect(&rect);
int nWidth = rect.right - rect.left + 1;
int nHeight = rect.bottom - rect.top + 1;
Graphics graphics(dc.m_hDC);
if ( ! m_pMemBitmap )
{
SHOW_TIME_START(t1, _T("Prepare off-screen bitmap"));
CreateOffScreeenGraphics(nWidth, nHeight);
SHOW_TIME_END(t1);
}
// draw from memory bitmap to window
graphics.DrawImage(m_pMemBitmap, 0, 0);
SHOW_TIME_END(t);
return 0;
}
// create off-screen graphics and draw to it
void CreateOffScreeenGraphics(int nWidth, int nHeight)
{
// Create off-screen bitmap
m_pMemBitmap = new Bitmap(nWidth, nHeight);
// Create off-screen graphics
m_pMemGraphics = Graphics::FromImage(m_pMemBitmap);
// draw to off-screen graphics
Draw(m_pMemGraphics, nWidth, nHeight);
}
// draw lines to Graphics
void Draw(Graphics* pGraphics, int nWidth, int nHeight)
{
// ...
}
protected:
ULONG_PTR m_gdiplusToken;
Bitmap* m_pMemBitmap;
Graphics* m_pMemGraphics;
// ...
Execution time in the Output window
Step 4 - using CachedBitmapWhile using a cashed bitmap we should be ready for the rare case when the Display Properties are changed. The Program checks the return value of CMainFrame()
{
m_pCachedBitmap = NULL;
// ...
}
// clean-up operations
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if ( m_pCachedBitmap )
delete m_pCachedBitmap;
// ...
}
// redraw window
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SHOW_TIME_START(t, _T("OnPaint"));
CPaintDC dc(m_hWnd);
RECT rect;
GetClientRect(&rect);
int nWidth = rect.right - rect.left + 1;
int nHeight = rect.bottom - rect.top + 1;
Graphics graphics(dc.m_hDC);
if ( ! m_pMemBitmap )
{
SHOW_TIME_START(t1, _T("Prepare off-screen bitmap"));
CreateOffScreeenGraphics(nWidth, nHeight, &graphics);
SHOW_TIME_END(t1);
}
// draw from cached bitmap to window
if ( graphics.DrawCachedBitmap(m_pCachedBitmap, 0, 0) != Ok )
{
// make bitmap again (display parameters are changed)
SetDirty();
CreateOffScreeenGraphics(nWidth, nHeight, &graphics);
graphics.DrawCachedBitmap(m_pCachedBitmap, 0, 0);
}
SHOW_TIME_END(t);
return 0;
}
// create off-screen graphics and draw to it
void CreateOffScreeenGraphics(int nWidth, int nHeight, Graphics* pGraphics)
{
// Create off-screen bitmap
m_pMemBitmap = new Bitmap(nWidth, nHeight);
// Create off-screen graphics
m_pMemGraphics = Graphics::FromImage(m_pMemBitmap);
// draw to off-screen graphics
Draw(m_pMemGraphics, nWidth, nHeight);
// Create cashed bitmap
m_pCachedBitmap = new CachedBitmap(m_pMemBitmap, pGraphics);
}
// draw lines to Graphics
void Draw(Graphics* pGraphics, int nWidth, int nHeight)
{
// ...
}
// cause to create offscreen graphics when window will be redrawn
void SetDirty()
{
// ...
if ( m_pCachedBitmap )
{
delete m_pCachedBitmap;
m_pCachedBitmap = NULL;
}
}
protected:
CachedBitmap* m_pCachedBitmap;
// ...
Execution time in Output window:
ConclusionUsing CashedBitmap in GDI+ allows to get acceptable drawing speed. Maybe in Windows XP results will be better. NotesI didn't find in GDI+ Windows API functions like:
Does anyone has some idea about this?
|
||||||||||||||||||||||