1

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

为了更好地理解,两张图片: 没有鼠标移动的应用程序 应用程序 w 野鼠标移动

一些代码:

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 显示为表单标题。

4

1 回答 1

5

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 值,这意味着此类代码可能导致致命的应用程序错误。相反,使用这样的代码:

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.ProcessMessages循环sleep()

于 2013-10-11T23:47:13.437 回答