为什么不使用GDI从数组中重复填充窗口的RGB数据?

17

这是对此问题的跟进。我正在编写一个简单的游戏,想找到在Win32窗口中以最快的方式(反复)显示RGB数据数组的方法,而且不会出现闪烁或其他伪像。

针对上一个问题的答案中推荐了几种不同的方法,但没有达成哪种方法最快的共识。因此,我编写了一个测试程序。该代码只是尽可能快地反复显示屏幕上的帧缓冲区。

以下是我在32位视频模式下运行32位数据时获得的结果 - 这可能会让一些人感到惊讶:

- Direct3D (1):             500 fps
- Direct3D (2):             650 fps
- DirectDraw (3):          1100 fps
- DirectDraw (4):           800 fps
- GDI (SetDIBitsToDevice): 2000 fps

考虑到以下数据:

  • 为什么很多人坚信GDI在这种操作中太慢了?
  • 是否有任何理由优先选择DirectDraw或Direct3D而不是SetDIBitsToDevice?

以下是每个Direct*代码路径调用的简要摘要。如果有人知道更有效使用DirectDraw / Direct3D的方法,请发表评论。

1. CreateTexture(D3DUSAGE_DYNAMIC, D3DPOOL_DEFAULT);
       LockRect(); memcpy(); UnlockRect(); DrawPrimitive()

2. CreateTexture(0, D3DPOOL_SYSTEMMEM); CreateTexture(0, D3DPOOL_DEFAULT);
       LockRect(); memcpy(); UnlockRect(); UpdateTexture(); DrawPrimitive()

3. CreateSurface(); SetSurfaceDesc(lpSurface = &frameBuffer[0]);
       memcpy(); primarySurface->Blt();

4. CreateSurface();
       Lock(); memcpy(); Unlock(); primarySurface->Blt();

2
我不是这个领域的专家,但在得出任何结论之前,我肯定会在几种不同类型的图形硬件上运行这个实验。 - 500 - Internal Server Error
你是如何组合RGB数据的?除非你需要硬件加速来组合图像,否则我不会期望DirectX更快。它需要以某种方式发送到视频硬件才能显示,即使通过GDI也是如此。使用DirectX方法会增加创建表面或纹理的额外工作。认为GDI必然会更慢是一个无关逻辑的论点。 - snarf
1
GDI最擅长的一件事情,也是它在过去20年中进行了优化的——就是移动位。现在似乎有一种传统的“智慧”,即任何20年历史的API都必须在某种程度上过时。 - Chris Becke
大家好!Paul,最终你选择了什么,为什么选择它? 我正在实时显示和更新一个大型的RGB数据数组,该数组代表进程的原始内存,并且Direct2D似乎不适合这种操作,因为我得到了非常低的帧速率。 - Patrick Samy
值得一提的是,你的DirectX测试循环都有一个巨大的缺陷,这可能解释了性能问题。需要理解的是,DirectX被优化为一次性地设置所有内容,然后在许多帧中重复使用,只改变需要更改的内容。正确的用法是仅在第一次创建Surface和Texture时调用CreateSurface和CreateTexture,然后在各个帧中重复使用这些实体。也就是说,这些调用不应该在计时循环内部。 - ToolmakerSteve
3个回答

7
这里有几件事需要记住。首先,“常识”很多都是基于一些不再适用的事实。
在AGP时代,当CPU直接与GPU通信时,它总是使用基本的PCI协议,这发生在“1x”的速率(始终和必然)。只有当GPU直接与内存控制器对话时,AGX 2x / 4x / 8x才会应用。换句话说,取决于你什么时间查看,GPU从内存加载纹理比CPU直接向GPU发送相同数据最多快8倍。当然,CPU到内存的带宽也远超过PCI总线的支持范围。
然而,当事情转向PCI-E时,这完全改变了。虽然路径可能会影响带宽,但并没有一般规则表明内存-> GPU比CPU-> GPU更快。现在(因为AGP已经成为历史),唯一比较安全的概括是,如果你有一个专门的图形卡,那么GPU几乎总是比主板上的主存储器更具有带宽。
但是,在你的情况下,并不太重要 - 无论如何,你都在考虑将数据从CPU空间移动到GPU空间。使用DirectX(或OpenGL)的主要速度差异在于,如果你尽可能地将计算集中在GPU上,并尽量避免使用CPU(或主存储器),那么这些API将提供实质性的内存->显示带宽改进。

我理解的对吗:在AGP机器上,“CPU => RAM => GPU”比“CPU => GPU”快8倍,因此在这些机器上更喜欢Direct*而不是GDI - 因为DirectDraw和Direct3D使用前者方法,而GDI使用后者方法? - user200783
@Paul:思考一下,倍增器可能超过8倍——如果我没记错的话,1x AGP已经比PCI更快了。无论如何,是的,这是一个大致的想法(虽然我认为至少有些驱动程序,GDI也可以通过内存完成工作)。 - Jerry Coffin
顺便说一下,AGP比PCI快得多的原因(即使在1x时)是因为它以66MHz运行,而不是PCI的33MHz,从而提供了双倍的带宽。因此,对于1x,总共提供了266MB / sec(66MHz * 32位)。在2x时为532MB / sec(66MHz * 2 * 32位),依此类推。有一定的协议开销,因此这些数字并不能完全实现。PCI-Express 1x v1以与AGP 1x相同的带宽运行。但可以组合到16x,使性能比AGP提高了一倍。每个版本都具有双倍的基本速率,这意味着PCIe 1x v3比AGP 1x快4倍... - Goz

4
杰瑞·科芬提出了一些有价值的观点。需要记住的是,在SetDIBitsToDevice中,DI代表设备无关性。这意味着你总是受驱动程序的限制。一些驱动程序曾经非常糟糕,严重影响了性能。DirectDraw也存在类似的问题...但你可以访问硬件blitter,所以它通常更有用。由于其与游戏相关联,IHV倾向于花更多时间编写适当的DirectDraw驱动程序。谁想成为性能堆栈的底部,当硬件完全能够做得更好时?
现在许多图形卡可以直接接受位数据,因此不会发生转换。如果确实需要调整,则在今天和时代中这也非常快。
与DirectDraw和D3D进行比较,你的Direct3D性能如此糟糕的原因是,Direct3D本质上是完全在GPU内部使用的,使用奇怪和复杂的格式来提高缓存性能等等。
再加上你没有像对比DDraw和D3D那样创建纹理/表面,锁定它,复制,解锁,然后通过各种方法绘制到后备缓冲区。为了获得最佳性能,最好直接使用DISCARD锁定后备缓冲区,然后在解锁之前直接进行memcpy。这将使你的性能更接近SetDIBitsToDevice。然而,由于上述原因,我仍然希望D3D比DDraw慢。

感谢您的回答。关于“锁定后备缓冲区”的问题 - 我知道这可以通过D3D实现,我将尝试在我的测试程序中添加对此的支持。您是否知道在(窗口化的)DDraw中也可以锁定后备缓冲区吗? - user200783
此外,您说在DirectDraw中可以获得“硬件混合器的访问权限”。除了简单地调用“IDirectDrawSurface7::Blt”之外,这是否需要进行任何其他工作? - user200783
@Paul:嗯,它是用DX6写的…所以很可能需要进行相当多的修改。 你可能在全屏模式下是正确的...因为我写它已经很久了。;) 但我不明白为什么这会有影响,因为你总是锁定一个离屏缓冲区(即后备缓冲区),对吧? - Goz
在窗口化的DirectDraw中,与全屏不同,您无法控制缓冲区翻转的发生时间。因此,没有任何东西可以阻止离屏(背景)缓冲区随时变成屏幕上的缓冲区。因此,我认为在窗口化的DirectDraw中是不允许锁定背景缓冲区的。可以锁定主表面并直接绘制到其中 - 但不幸的是,这会导致Windows Vista/7禁用桌面组合。 - user200783

2
你会听到人们批评GDI的原因是它曾经只是旧的Windows API调用。它的新版本(在我上次查看时称为GDI+)实际上只是放在DirectX调用之上的API。因此,有时从编程角度来看,使用GDI可能似乎相当简单,但在事物之间添加一层始终会减慢速度。正如Jerry Coffin的回答中提到的那样,你的示例涉及移动数据,这是慢速的原因。我有点惊讶DirectX要慢那么多,但如果不深入研究DirectX文档(这些文档实际上一直非常棒..建议访问www.codesampler.com),我就无法提供更多帮助。我总是能够从这个网站找到好的起点,并且实际上,虽然我可能因此而疯狂,但我发誓DirectX SDK在文档和示例方面的改进都是基于这个家伙的工作完成的!
至于DirectDraw与Direct3D(而不是GDI调用)的讨论。我建议选择Direct3D。我认为自8.0以来,DirectDraw已被弃用,而9.0已经存在很长时间了。最后,所有的DirectX都是3D的,只是周围有不同级别的有用的2D API,但在实际使用3D空间时,你可能会发现在2D环境中可以做一些非常有趣的事情。(我曾经为太空入侵克隆游戏创建过一个相当不错的随机生成闪电武器:))
总之,希望这能帮到你!
PS:需要注意的是,DirectX并不总是最快的。对于键盘输入(除非在10或11中有所改变),通常建议使用Windows事件..因为DirectInput实际上只是该系统的包装器!..但XInput确实很棒!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接