【问题标题】:Delphi "While" Performance decreases when mouse not moving over FireMonkey Application当鼠标不在 FireMonkey 应用程序上移动时,Delphi “While”性能会降低
【发布时间】:2013-10-11 23:28:00
【问题描述】:

我正在玩一些 Delphi + openGL。因为懒,想用FireMonkey给我做个表格。
所以我做了一个 FireMonkeyHD 应用程序,初始化了 GL,渲染了一个基本的立方体......并发现了一些奇怪的行为。当我不移动鼠标时,我得到大约 10FPS。当我移动鼠标时,性能轻松提升到 500FPS 甚至(显然)更高。那可能是什么?
*注意:我在主线程中使用 onKeyDown 事件开始渲染...

为了更好地理解,两张图:

一些代码:

unit Unit1;

interface

uses
{ ... }
;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char;
      Shift: TShiftState);
  private
    degen
    : IDeGEn;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.FormCreate(Sender: TObject);
var
  DeGEnFactory
  : TDeGEnFactory;
begin
  { ... }
  // Load DeGEn
  degen := DeGEnFactory.newDeGEn(WindowHandleToPlatform(Form1.Handle).Wnd);

  // Initialize
  degen.get3D.init(600, 800);
  degen.get3D.setOnRender(function : Boolean
  var
    v3d
    : R3DVector;
  begin
    Result := true;
    self.Caption := IntToStr(degen.get3D.getFPS);
    v3d.z := 0.01;
    degen.get3D.getCamera.move(v3d);
    degen.get3D.renderTest;
  end);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  // Shut down DeGEn
  { ... }
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char;
  Shift: TShiftState);
begin
  // Start rendering
  degen.startRendering;
end;

end.


startRendering 看起来像这样:

procedure TDeGEn.startRendering;
var
  msg
  : TMsg;
begin
  if isRendering then
  begin
    Exit;
  end;

  isRendering := true;
  while GetMessage(msg, 0, 0, 0) do
  begin
    TranslateMessage(msg);
    DispatchMessage(msg);

    if not degen3D.render then
    begin
      Break;
    end;
  end;

  isRendering := false;
end;


您可能很容易注意到,相机只是以取决于 FPS 的速度远离立方体。我还将 FPS 显示为表单标题。

【问题讨论】:

  • 这是非常老的 Windows 应用程序中的常见行为。除非屏幕被标记为“脏”,否则他们不会重绘屏幕。将鼠标移到屏幕的一部分上是一种将控件标记为脏并强制重新绘制的可靠方法。这可能不是您的情况的罪魁祸首,但我怀疑它与您的消息泵有某种联系,并且移动光标会增加您的消息泵中发生绘图所需的某些事情的频率。
  • 说到魔鬼,我相信这是你的罪魁祸首:while GetMessage(msg, 0, 0, 0) do。如果您没有待处理的窗口消息,则完全跳过循环。已经有一段时间了,Pascal 有 Do While 吗? :) 无论是否有等待消息,您都希望至少调用一次此循环以让 degen3D.render 做某事。
  • 好吧,我能看到的唯一渲染代码部分是在您的消息泵中。如果没有待处理的消息,您的消息泵循环将提前退出。
  • @Andon 你搞错了。当队列为空时,GetMessage 会阻塞。
  • @Marco 为什么要添加消息循环?您的应用已经有一个。为什么需要第二个?

标签: delphi opengl firemonkey onkeydown


【解决方案1】:

GetMessage 等待收到消息。 如果您不移动鼠标,则很少有消息进入消息队列并且渲染会很慢,因为 CPU 一直在等待 GetMessage 返回。

当您移动鼠标时,会创建大量消息;消息队列已满,GetMessage 几乎立即返回。

请注意,从 Windows 3.1 开始就不需要执行这样的消息循环了。

另请注意,Microsoft 警告不要像这样实现消息循环。
来自:http://msdn.microsoft.com/en-us/library/windows/desktop/ms644936%28v=vs.85%29.aspx

因为返回值可以是非零、零或-1,所以避免使用类似的代码 这个:

while (GetMessage( lpMsg, hWnd, 0, 0)) ...

如果 hWnd 是一个 -1 返回值的可能性 无效参数(如引用一个已经被 destroy) 意味着此类代码可能导致致命的应用程序错误。 相反,使用这样的代码:

BOOL bRet;`

while( (bRet = GetMessage( &msg, hWnd, 0, 0 )) != 0) {
    if (bRet == -1)` `    {
        // handle the error and possibly exit
    } else {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    } 
}

无论如何,没有必要做那样的循环。
而是在表单上放置一个计时器,并将代码放在OnTimer 事件中。

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  //DoRendering
end;

如果普通计时器太慢,则有很多高分辨率计时器。 JVCL 已经做到了,unDelphiX 也有。
见这里:http://delphi.about.com/od/windowsshellapi/a/delphi-high-performance-timer-tstopwatch.htm
或在这里:http://wiki.delphi-jedi.org/wiki/JVCL_Help:TJvTimer

在 CPU 密集型循环中处理 Windows 消息
我们不再纠结于消息循环(从 Delphi 1.0 开始)。
如果您发现应用程序由于循环占用所有 CPU 时间而没有响应,请改用 Application.ProcessMessages

WM_TIMER 消息的优先级较低
如果您使用默认计时器,则会遇到不可靠性问题。
这是因为 Windows 将WM_TIMER 消息(TTimer 查找的消息)视为低优先级。
如果 Windows 正忙于其他任务,它会将多个等待的 WM_TIMER 消息压缩为一个,以避免创建计时器消息的积压。
它对 WM_PAINT 消息做同样的事情。
见:http://msdn.microsoft.com/en-us/library/windows/desktop/ms644902%28v=vs.85%29.aspx

避免这种情况的一个技巧是使用高分辨率计时器来构造循环(这不依赖于消息循环),或者使用带有Application.ProcessMessagessleep() 延迟的简单无限循环。

【讨论】:

  • 尝试了 while(true) w/o GetMessage 并尝试了 PeekMessage。没有变化:(
  • 嗯,您的编辑确实解决了问题,非常感谢。但不幸的是,它没有回答“那可能是什么”这个问题,因为正如我所提到的,即使没有 GetMessage,速度也会下降。
  • 删除整个消息,TranslateMessage 和 DispatchMessage 也会减慢速度。只需使用计时器。
  • 是的,这就是我现在正在做的事情,但我仍然想知道为什么会发生这种情况,以便将来避免这个问题。如果我不使用 GetMessage/PeekMessage,循环不会变慢!
  • TranslateMessageDispatchMessage 正在减慢速度。
猜你喜欢
  • 1970-01-01
  • 2012-06-22
  • 1970-01-01
  • 1970-01-01
  • 2014-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-16
相关资源
最近更新 更多