Maru
Maru

Reputation: 942

Delphi "While" Performance decreases when mouse not moving over FireMonkey Application

I am playing around a little with Delphi + openGL. Because I am lazy, I wanted to use FireMonkey to make a form for me.
So I made a FireMonkeyHD application, initialized GL, rendered a basic cube... and found some strange behavior. When I do not move my mouse, I get about 10FPS. When I move my mouse, performance rises easily to 500FPS and (obviously) more. What could that be?
*Note: I start rendering with an onKeyDown Event in the Main Thread...

For a better understanding, two pics: Application w/o mouse move Application w wild mouse move

Some code:

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.


And startRendering looks like this:

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;


As you might easily notice, the camera just moves away from the cube with a speed dependent on the FPS. Also I get the FPS displayed as form caption.

Upvotes: 1

Views: 976

Answers (1)

Johan
Johan

Reputation: 76537

The GetMessage waits for a message. If you don't move the mouse very little messages get into the message-queue and rendering will be slow, because the CPU is stuck waiting for GetMessage to return.

When you move the mouse lots of messages get created; the message-queue is full and GetMessage returns almost instantly.

Note that doing a messageloop like this has not been necessary since Windows 3.1.

Also note that Microsoft warns against implementing the messageloop like this.
From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms644936%28v=vs.85%29.aspx

Because the return value can be nonzero, zero, or -1, avoid code like this:

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

The possibility of a -1 return value in the case that hWnd is an invalid parameter (such as referring to a window that has already been destroyed) means that such code can lead to fatal application errors. Instead, use code like this:

BOOL bRet;`

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

Anyway there is no need to do the loop like that.
Instead put a timer on the form, and put the code inside the OnTimer event.

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

If the normal timer is too slow, there are a great many high resolution timers out there. JVCL has done and unDelphiX has one too.
See here: http://delphi.about.com/od/windowsshellapi/a/delphi-high-performance-timer-tstopwatch.htm
or here: http://wiki.delphi-jedi.org/wiki/JVCL_Help:TJvTimer

Handling Windows messages in a CPU intensive loop
We do not muck about with the messageloop anymore (not since Delphi 1.0).
Use Application.ProcessMessages instead if you find that the application is unresponsive due to your loop hogging all CPU time.

WM_TIMER messages are low priority
If you use the default timer, you will run into unreliability issues.
This is because Windows regards WM_TIMER messages (the messages that TTimer looks for) as low priority.
If Windows is busy with other tasks it will compress multiple waiting WM_TIMER messages into one to avoid creating a backlog of timer messages.
It does the same thing with WM_PAINT messages.
See: http://msdn.microsoft.com/en-us/library/windows/desktop/ms644902%28v=vs.85%29.aspx

One trick to avoid this is to structure the loop using a high resolution timer (this does not depend on the message loop), or to use a simple endless loop with Application.ProcessMessages and a sleep() delay.

Upvotes: 5

Related Questions