1

我目前正在将一些使用 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 在它认为合适的时候刷新渲染目标?

4

2 回答 2

0

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

于 2020-05-21T21:24:02.933 回答
0

我发现了这个问题。

以下语句在为 Direct2D 创建渲染目标时使用默认的 present 选项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
);

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

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

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

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

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

于 2020-05-21T20:07:39.243 回答