16
votes

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.

3
You didn't show the drawing code of D2Dzdd
The drawing code is standerd Direct2D code to draw an ellipse. I use the FillEllipse and DrawEllipse function and nothing else thats why i didnt post it.roohan
The main purpose for introducing Direct2D was a new clean API and the base framework for DirectWrite. Drawing Text is todays most expensive operation and DirectWrite has a huge speed improvement for high quality text. Maybe you should consider testing with other basic drawing primitives. I doubt your app is only drawing elipses.Lothar

3 Answers

40
votes

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.

5
votes

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?

1
votes

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;
    }
}