我目前正在将我的一些使用C#(FW版本4.7.2)WinForms实现的老游戏用C++转换为Direct3D。
目前,我所有的实时图形游戏都是用C#(FW版本4.7.2)实现的。OnPaint
覆盖,绘制到从图形对象中检索到的 PaintEventArgs
参数,并在绘制完当前帧后立即失效。我不知道是不是不推荐这样做,但很好用)。当然,这些应用程序使用双缓冲。这导致了一个核心的100%利用率,这是确定的。
在将我的游戏移植到C++和Direct3D时,我遇到的第一个障碍是窗口的刷新率,即使我通过实现相同的 WndProc()
并称 InvalidateRect()
画完之后。
我按照这个快速入门指南。https:/docs.microsoft.comen-uswindowswin32direct2ddirect2d-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帧/秒,而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在它认为合适的时候刷新渲染目标?
我已经找到了问题所在。
下面的语句,在为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
);
通过将当前选项改为立即,WM_PAINT消息在调用了 InvalidateRect()
D2D1::HwndRenderTargetProperties(m_hwnd, size, D2D1_PRESENT_OPTIONS_IMMEDIATELY),
所以我太急于问这个问题了,因为让刷新率超过显示器的刷新率并没有任何影响,而我从中得到的是Direct2D引擎通过等待实际屏幕的下一次刷新来优化cpu的使用,这很有道理。
试图绘制超过显示器刷新率的画面,看起来是在浪费CPU周期。
需要考虑实现一个好的定时机制,因为winforms和GDI大部分时间都比显示器刷新率慢(当你在屏幕上有很多精灵和几何图形时),现在我们比这个快,应该适应。
InvalidateRect(hwnd,......本身会在有hwnd句柄的窗口上发射WM_PAINT消息。你刚刚创建了一个循环引用(race)。