【问题标题】:WM_PAINT message frequency : C# (.Net Framework 4.7.2 WinForms) vs. C++WM_PAINT 消息频率:C# (.Net Framework 4.7.2 WinForms) vs. C++
【发布时间】:2020-09-08 09:57:41
【问题描述】:

我目前正在将一些使用 C#(FW 版本 4.7.2)WinForms 实现的旧游戏转换为使用 C++ 的 Direct3D。

目前,我所有的实时图形游戏都实现了OnPaint 覆盖,绘制到从PaintEventArgs 参数检索到的Graphics 对象中,并在绘制当前帧后立即失效。 (我不知道这是否不推荐,但效果很好)。这些应用程序当然使用双缓冲。这会导致一个核心 100% 的利用率,这是可以的。

将我的游戏移植到 C++ 和 Direct3D 时遇到的第一个障碍是窗口的刷新率,即使我通过实现 WndProc() 并在绘制后调用 InvalidateRect() 做了同样的事情。

我遵循了这个快速入门指南:https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-quickstart

问题是,

使用我的 Windows 窗体应用程序,在全尺寸背景图像上绘制时刷新率约为 60 fps,在空白背景上绘制时刷新率为 300-400 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();
    }
}

不用说,我期待在移植到 DirectX 时帧速率会更好,但问题是 WM_PAINT 事件没有足够频繁地触发,即使我不绘制任何东西,我也卡在 100 fps,并且CPU 利用率仅为 10% 左右。

我只在快速入门指南的源代码中添加了以下一行:

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

我做错了什么?

我读到Direct 2D的渲染目标是自动双缓冲的,

所以问题是,为什么 WM_PAINT 事件每秒只触发 100 次?

注意:检查了 windows 窗体 Control.Invalidate() 方法源,它的作用是调用 InvalidateRect(如果子控件也要失效,则调用 RedrawWindow())

注意:我是否在此处错误地使用了直接 2d 绘图?我是否应该在一个单独的线程上持续绘制并让 DirectX 在它认为合适的时候刷新渲染目标?

【问题讨论】:

    标签: c# c++ .net direct2d wm-paint


    【解决方案1】:

    我找到了问题。

    以下语句在为 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
    );
    

    通过将当前选项更改为立即,在调用 InvalidateRect() 后立即收到 WM_PAINT 消息

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

    所以我问这个问题太快了,因为刷新率高于显示器的刷新率没有任何影响,我从中得到的是 Direct2D 引擎正在通过等待优化 cpu 使用率实际屏幕的下一次刷新,这很有意义。

    尝试绘制超过显示器刷新率的内容看起来像是在浪费 CPU 周期。

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

    【讨论】:

    • 使用 Direct2D 和所有相关层(DXGI、Direct Composition 等),您甚至根本不需要 WM_PAINT,既不是 HDC 也不是 GDI(如果您需要它,还有互操作的可能性)。您构建一个交换链(类似于双缓冲,使用 DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL),尽快绘制后台缓冲区(D2D RenderTarget/DeviceContext),并同步交换缓冲区(调用 IDXGISwapChain::Present):docs.microsoft.com/en-us/windows/win32/direct2d/… Direct2D 本身不优化任何东西(除了它使用 GPU 的事实)。
    • @SimonMourier 感谢您的评论。没有优化,你是对的。我认为效率低下在于 Forms 框架,因为 windows 窗体 Control.Invalidate() 方法试图立即绘制到表面(从而导致 fps 值高于实际屏幕刷新率,同时导致 100% cpu)。
    【解决方案2】:

    InvalidateRect(hwnd,.. 本身会在带有 hwnd 句柄的窗口上触发 WM_PAINT 消息。您刚刚创建了一个循环引用 (race)。

    【讨论】:

      猜你喜欢
      • 2020-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-04
      • 1970-01-01
      • 1970-01-01
      • 2012-09-09
      相关资源
      最近更新 更多