1
votes

I am currently converting some of my old games implemented using C# (FW version 4.7.2) WinForms to Direct3D using C++.

Currently, all my real-time graphics games implement the OnPaint override, draw into the Graphics object retrieved from the PaintEventArgs parameter and invalidate right after drawing the current frame. (I don't know if this is not recommended, but it works great). These apps use double buffering of course. This causes 100% utilization of one core, which is OK.

The first obstacle I came accross when porting my games to C++ and Direct3D is the refresh rate of the window even if I had done the same thing by implementing the WndProc() and calling InvalidateRect() after drawing.

I followed this quick start guide: https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-quickstart

The problem is,

With my windows forms apps, the refresh rate is around 60 fps drawing over a full-size background image, 300-400 fps when drawing on an empty background. The following example, drawing only a rectangle, produces an amazing 2,500 fps:

public class Surface : Form
{
    private int fps = 0;
    private int lastFPS = 0;
    private double lastSeconds = 0;
    private Stopwatch chronometer = Stopwatch.StartNew();
    Font font = new Font("Courier New", 10f);

    public Surface()
    {
        BackColor = Color.Black;
        DoubleBuffered = true;
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.DrawRectangle(Pens.White, 100, 100, 100, 100);
        e.Graphics.DrawString("FPS: " + lastFPS, font, Brushes.White, 5, 5);

        fps++;
        if (chronometer.Elapsed.Seconds > lastSeconds)
        {
            lastSeconds = chronometer.Elapsed.Seconds;
            lastFPS = fps;
            fps = 0;
        }
        Invalidate();
    }
}

Needless to say, I was expecting a much better frame rate when porting to DirectX, but the problem is that the WM_PAINT event is not fired frequently enough and I am stuck at 100 fps even if I don't draw anything, and cpu utilization is around 10% only.

I only added the following line to the source code of the quick start guide:

case WM_PAINT:
{
    pDemoApp->OnRender();
    ValidateRect(hwnd, NULL);
    InvalidateRect(hwnd, NULL, TRUE); // THIS IS THE LINE I'VE ADDED
}

What am I doing wrong?

I read that the rendering target of Direct 2D is automatically double buffered,

So the question is, why the WM_PAINT event gets fired only 100 times per second?

Note: Checked the windows forms Control.Invalidate() method source, and what it does is to call InvalidateRect (or RedrawWindow() if child controls are to be invalidated as well)

NOTE: Am I using direct 2d drawing incorrectly here? Should I continuously draw on a separate thread and let DirectX refresh the rendering target as it sees fit?

2

2 Answers

0
votes

I've found the issue.

The following statement, while creating the render target for Direct2D, uses the default present option D2D1_PRESENT_OPTIONS_NONE, which causes the rendering engine to wait for the vSync, thus the maximum frames per second on my laptop equals 60 Hz (the refresh speed of the laptops monitor)

// Create a Direct2D render target.
hr = m_pDirect2dFactory->CreateHwndRenderTarget(
    D2D1::RenderTargetProperties(),
    D2D1::HwndRenderTargetProperties(m_hwnd, size),
    &m_pRenderTarget
);

By changing the present option to immediate, the WM_PAINT message is received right after calling InvalidateRect()

D2D1::HwndRenderTargetProperties(m_hwnd, size, D2D1_PRESENT_OPTIONS_IMMEDIATELY),

So I was too quick to ask this question, because having a refresh rate more than the refresh rate of the monitor doesn't have any effect and what I get from this is that the Direct2D engine is optimizing cpu usage by waiting for the next refresh of the actual screen, which makes good sense.

Trying to draw more than the monitor refresh rate looks like a waste of CPU cycles.

Need to consider implementing a good timing mechanism, because winforms and GDI is most of the time slower than the monitor refresh rate (when you have many sprites and geometries on the screen), and now we are faster than that and should adapt.

0
votes

InvalidateRect(hwnd,.. itself fires WM_PAINT message on a window with hwnd handle. You've just created a circular reference (race).