Reputation: 751
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.
Upvotes: 19
Views: 20392
Reputation: 155
Of course, as other posters mentioned, in hardware-accelerated graphics important thing is caching resources, although there are complicated cases.
I wrote some solution that draws rectangles with some padding, filling the screen. On 1980x1080 there are about 300 rectangles and there are about 40 fps.
The most important thing is that Direct2D seems to be not the fastest solution, at least because Direct2D is just a wrapper on d3d11 (or maybe 12). If You inspect Chromium (at least Edge) render window via Spy++, You will notice, that it’s class is called “Intermediate D3D Window”.
I am sure (it’s open source though) that Blink’s graphics (Skia seems to be) even for stuff like just drawing primitives is written using exactly D3D and not Direct2D. As well as, uwp apps, for example.
Upvotes: 0
Reputation: 11
I'm working on d2d. Basically it is around 2x or 3x more fast than gdi, if you stay on basic drawing. Drawing line, rectangles, ellipses, .. Like RenderTarget->DrawLine. Globally, drawing directly on the render target is 2x 3x more fast than gdi. Also don't forget to draw on a d2d backbuffer instead of the hwnd. It's exactly the same process as using a gdi bitmap backbuffer but using direct2d resources.
If you want to use the advanced d2d objects, like geometry, this is another thing.
By example, you cannot move a rectangle without creating an instance of a transformed geometry of this rectangle.
D2D resources are immutables, despite they are managed at the cpu level, you cannot modify the source shape and just draw it. You have to create a copy of this shape for each translations, rotations...
This is not a big problem if you use an app drawing like paint... But if you want to use a real time system, with a quantity of shapes, mouse hit testing, scaling, scrolling, etc... Then that can give a hole in performances.
On my tests, it take me (in debug mode) around 0.5s to transform/translate a source geometry 1000000 times.
Code test example:
void TestGeometryPerf() { //1000000 = 0.35s/0.45s (in msvc 2019 debug mode)
ID2D1RectangleGeometry *r;
ID2D1TransformedGeometry *t;
D2D1_RECT_F rect = D2D1::RectF(0, 0, 1, 1);
D2D1::Matrix3x2F matrix = D2D1::Matrix3x2F::Translation(10,10);
//create geometry source
m_factory->CreateRectangleGeometry(rect, &r);
for(int x = 0; x < 1000000; x++) {
//create a transformed geometry from geometry source
m_factory->CreateTransformedGeometry(r, matrix, &t);
if( t->Release() != 0) {
throw;
}
}
if( r->Release() != 0) {
throw;
}
}
Upvotes: 1
Reputation: 698
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.
Upvotes: 47
Reputation: 3447
Some time ago I’ve refused migrating rendering code from GDI to Direct2D due to low performance. As I understand from google, Direct2D performance depends on driver and hardware optimizations and you shouldn’t expect the same speed on different hardware. GDI is pretty old and works equally almost everywhere.
Must say I’ve tried to use it for drawing simple geometry primitives whereas Direct2D seems to be much more robust library and maybe there could be performance boost on complex scenarios, but this is not my case.
If you need GDI performance with better quality – try to use OpenGL or Direct3D directly.
This is a related question: Is TDirect2DCanvas slow or am I doing something wrong?
Upvotes: 6