I'm programming a simulation at the moment, and I want to port my application from using GDI to using Direct2D. But my Direct2D code is much slower than my GDI code.
I render a lot of ellipses on the screen. In my GDI application I draw to a memory device context and then use BitBlt to draw on the windows device context. With Direct2D, I draw onto a ID2D1HwndRenderTarget.
My Problem is, when using GDI, I can draw easily 400+ ellipses and still have 400 FPS. When I do the same number of ellipses with Direct2D, my FPS drops down to 30FPS.
I already switched antialiasing off but it doesn't really help. The interesting thing is that drawing just a few ellipses is faster in Direct2D compared to GDI. Is there anything I can do to improve the performance in Direct2D, or should I keep my application using GDI?
Here is my drawing code using GDI:
VOID Begin() {
SelectObject(this->MemDeviceContext, this->MemoryBitmap);
this->BackgroundBrush = CreateSolidBrush(this->BackgroundColor);
HBRUSH OldBrush = (HBRUSH)SelectObject(this->MemDeviceContext, this->BackgroundBrush);
Rectangle(this->MemDeviceContext, -1, -1, 801, 601);
SelectObject(this->MemDeviceContext, OldBrush);
DeleteObject(this->BackgroundBrush);
SetViewportOrgEx(this->MemDeviceContext, 400, 300, &this->OldOrigin);
}
VOID End() {
SetViewportOrgEx(this->MemDeviceContext, this->OldOrigin.x, this->OldOrigin.y, 0);
BitBlt(this->DeviceContext, 0, 0, 800, 600, this->MemDeviceContext, 0, 0, SRCCOPY);
}
Between my Begin and End function, I draw my ellipses the standard GDI way.
Here are my begin and end functions using Direct2D:
VOID BeginDrawing() {
this->RenderTarget->BeginDraw();
RenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::CornflowerBlue));
RenderTarget->SetTransform(this->ScalingMatrix * this->TranslateMatrix);
}
VOID EndDrawing() {
this->RenderTarget->EndDraw();
}
And here is how I set up my Direct2D interfaces. It's all wrapped in class; that's why I cant post the full code:
if(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &Direct2DFactory) != S_OK)
throw std::runtime_error("RENDERWINDOW::InitializeDirect2D: Failed to create a factory interface.");
RECT WindowRect;
memset(&WindowRect, 0, sizeof(RECT));
GetClientRect(this->WndHandle, &WindowRect);
D2D1_SIZE_U WindowSize = D2D1::SizeU(WindowRect.right, WindowRect.bottom);
Direct2DFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_HARDWARE),
D2D1::HwndRenderTargetProperties(this->WndHandle, WindowSize, D2D1_PRESENT_OPTIONS_IMMEDIATELY), &RenderTarget);
Thank you in advance.
A common mistake with first attempts at Direct2D is developers do not properly cache the D2D resources and instead create and destroy resources too often. If all your ellipses are similar sized, you should create and cache this ellipse object once. If you have 30 different sizes/shapes, create ellipse versions for all 30 sizes/shapes only once. This significantly speeds up Direct2D. Same goes for Rectangles and all other primitives. Scaling a cached object versus repeated creation/destruction is also a solution for some scenarios if too many variations exist for a primitive, though using a resource at its native size is ideal and memory cards have quite a bit of memory to store your resources.
Gdi ellipses look absolutely terrible and using Direct3D directly is fairly complex, especially for ellipses, large polygons, and higher level primitives. With proper use of Direct2D you should be able to get good speed and high quality rendering.