简体   繁体   English

WM_PAINT 消息频率:C# (.Net Framework 4.7.2 WinForms) vs. C++

[英]WM_PAINT message frequency : C# (.Net Framework 4.7.2 WinForms) vs. C++

I am currently converting some of my old games implemented using C# (FW version 4.7.2) WinForms to Direct3D using C++.我目前正在使用 C#(固件版本 4.7.2)WinForms 使用 C++ 将我的一些旧游戏转换为 Direct3D。

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.目前,我所有的实时图形游戏都实现了OnPaint覆盖,绘制到从PaintEventArgs参数检索到的 Graphics object 并在绘制当前帧后立即失效。 (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.这会导致一个核心 100% 的利用率,这是可以的。

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.在将我的游戏移植到 C++ 和 Direct3D 时,我遇到的第一个障碍是 window 的刷新率,即使我通过实现WndProc()并在绘图后调用InvalidateRect()做了同样的事情。

I followed this quick start guide: https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-quickstart我遵循了这个快速入门指南: 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.使用我的 windows forms 应用程序,在全尺寸背景图像上绘制时刷新率约为 60 fps,在空白背景上绘制时为 300-400 fps。 The following example, drawing only a rectangle, produces an amazing 2,500 fps:以下示例仅绘制一个矩形,产生惊人的 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.不用说,在移植到 DirectX 时,我期待更好的帧速率,但问题是 WM_PAINT 事件没有足够频繁地触发,即使我不绘制任何东西,我也会卡在 100 fps,并且 cpu 利用率是仅约 10%。

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,我读到 Direct 2D 的渲染目标是自动双缓冲的,

So the question is, why the WM_PAINT event gets fired only 100 times per second?所以问题是,为什么 WM_PAINT 事件每秒只触发 100 次?

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)注意:检查了 windows forms Control.Invalidate()方法源,它的作用是调用 InvalidateRect (或 RedrawWindow() 如果子控件也要失效)

NOTE: Am I using direct 2d drawing incorrectly here?注意:我是否在这里错误地使用了直接 2d 绘图? Should I continuously draw on a separate thread and let DirectX refresh the rendering target as it sees fit?我是否应该在一个单独的线程上持续绘制并让 DirectX 在它认为合适的时候刷新渲染目标?

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)以下语句在为 Direct2D 创建渲染目标时,使用默认的当前选项D2D1_PRESENT_OPTIONS_NONE ,这会导致渲染引擎等待 vSync,因此我的笔记本电脑上每秒的最大帧数等于 60 Hz(笔记本电脑显示器的刷新速度)

// 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()通过将当前选项更改为立即,在调用InvalidateRect()后立即收到 WM_PAINT 消息

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.所以我问这个问题太快了,因为刷新率高于显示器的刷新率没有任何影响,我从中得到的是 Direct2D 引擎通过等待下一次刷新来优化 CPU 使用率实际屏幕,这很有意义。

Trying to draw more than the monitor refresh rate looks like a waste of CPU cycles.试图绘制超过显示器刷新率的内容看起来像是在浪费 CPU 周期。

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.需要考虑实现一个好的计时机制,因为 winforms 和 GDI 大部分时间都比显示器刷新率慢(当屏幕上有很多精灵和几何图形时),现在我们比这更快,应该适应。

InvalidateRect(hwnd,.. itself fires WM_PAINT message on a window with hwnd handle. You've just created a circular reference (race). InvalidateRect(hwnd,.. 本身会在带有 hwnd 句柄的 window 上触发 WM_PAINT 消息。您刚刚创建了一个循环引用(种族)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM