1
\$\begingroup\$

I'm making a simple openGL application and so far I can get the window to display, the update/render loop work, and it processes windows events (like WM_QUIT and WM_MOUSE). I've found though that there is a noticeable lag between, say, pressing the space bar, and receiving the event in the WndProc function (in this case, WM_KEYDOWN.) Is this a result of using WndProc and not some fancy direct input system? Is there a better way of getting, say, the mouse position?

I can even tie one of my objects to draw itself at the current mouse position (updated in that same WndProc function) and while it does so fine, to the proper coordinates, it's always behind the mouse. I can post some code if you guys are interested but it's about the most standard update loop / event processing code out there.

Thanks for the help!

EDIT: Since you're curious, here's the main():

int main(int argc, char **argv){
  MSG msg;

  if (!createEngineWindow("Expanse")){
    destroyEngineWindow();
    return 1;
  }

  ShowWindow(hWnd,SW_SHOW);     // Show The Window
  SetForegroundWindow(hWnd);    // Slightly Higher Priority
  SetFocus(hWnd);             // sorry, other windows

  initGL();
  startup();


  updatePreviousTime=updateCurrentTime
                    =renderPreviousTime
                    =renderCurrentTime
                    =static_cast<float>(clock())/CLOCKS_PER_SEC; // start the stopwatches
  while (!done){
    previousMousePosition=mousePosition;

    while (PeekMessage(&msg,NULL,0,0,PM_REMOVE)){
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    updatePreviousTime=updateCurrentTime;
    updateCurrentTime=static_cast<double>(clock())/CLOCKS_PER_SEC;
    uDT=updateCurrentTime-updatePreviousTime;
    updatePhase(uDT);

    if (updateCurrentTime>0.01 && uDT>0.01){ // ignore the first few frames
      double framerate=1.0/uDT;
      averagedFrameRate=Lerp<double>(averagedFrameRate,
                                     framerate,
                                     0.3);
    }

    renderPreviousTime=renderCurrentTime;  
    renderCurrentTime=static_cast<double>(clock())/CLOCKS_PER_SEC;
    rDT=renderCurrentTime-renderPreviousTime;
    renderPhase(rDT);

    SwapBuffers(hDC);
  }

  cleanUp();
}

And the WinProc, which handles the DispatchMessage thing (or TranslateMessage, or something.)

LRESULT CALLBACK WndProc(HWND hWnd,       // Handle For This Window
                         UINT uMsg,       // Message For This Window
                         WPARAM wParam,   // Additional Message Information
                         LPARAM lParam){  // Additional Message Information

  switch (uMsg){
    case WM_CLOSE:        // Did We Receive A Close Message?
      PostQuitMessage(0); // Send A Quit Message (we're going, hold on already.)
      done=true;
      return 0; // Jump Back
    case WM_ACTIVATE:
      if (!HIWORD(wParam))  // Check Active state
        windowActive=true;  // Program Is Active
      else
        windowActive=false; // Program Is No Longer Active
      return 0;

    case WM_MOUSEMOVE:
      if (!ignoreMouseMove)
        mouseMove(LOWORD(lParam),
                  HIWORD(lParam));
      else
        ignoreMouseMove=false;
  }
  return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

and the window creation:

bool createEngineWindow(string title){
  int PixelFormat;    // Holds The Results After Searching For A Match
  WNDCLASS wc;        // Windows Class Structure
  DWORD dwExStyle;    // Window Extended Style
  DWORD dwStyle;      // Window Style

  // Window Sizing and Position
  screenWidth=GetSystemMetrics(SM_CXSCREEN);
  screenHeight=GetSystemMetrics(SM_CYSCREEN);
  RECT WindowRect;
  WindowRect.left=(long)0;
  WindowRect.right=(long)screenWidth;
  WindowRect.top=(long)0;
  WindowRect.bottom=(long)screenHeight;

  // Windows Style
  hInstance=GetModuleHandle(NULL);  // Grab An Instance For Our Window
  wc.style=CS_OWNDC |               // We want our own device context
           CS_DBLCLKS;              // We want double clicks too
  wc.lpfnWndProc=(WNDPROC)WndProc;  // Our WndProc handles events first.
  wc.cbClsExtra=0;                  // No Extra Window Data
  wc.cbWndExtra=0;                  // No Extra Window Data
  wc.hInstance=hInstance;           // Set The Instance
  wc.hIcon=LoadIcon(NULL,           // Load The Default Icon
                    IDI_WINLOGO);
  wc.hCursor=LoadCursor(NULL,             // Load The Arrow Pointer
                        IDC_ARROW);
  wc.hbrBackground=NULL;            // No Background Required For GL
  wc.lpszMenuName=NULL;             // We Don't Want A Menu
  wc.lpszClassName="TorqWindow";    // Set The Class Name

  if (!RegisterClass(&wc)){
    return false;
  }

  // Fullscreen settings
  DEVMODE dmScreenSettings;         // Device Mode
  memset(&dmScreenSettings,         // Makes Sure Memory's Cleared
         0,
         sizeof(dmScreenSettings));
  dmScreenSettings.dmSize=sizeof(dmScreenSettings); // Size Of The Devmode Structure
  dmScreenSettings.dmPelsWidth=WindowRect.right; // Selected Screen Width
  dmScreenSettings.dmPelsHeight=WindowRect.bottom; // Selected Screen Height
  dmScreenSettings.dmBitsPerPel=24; // Selected Bits Per Pixel
  dmScreenSettings.dmFields=DM_BITSPERPEL // What the Devmode will set (it could also work with printers.
                          | DM_PELSWIDTH
                          | DM_PELSHEIGHT;
  if (!ChangeDisplaySettings(&dmScreenSettings,
                             CDS_FULLSCREEN))
    return false;

  dwExStyle=WS_EX_APPWINDOW; // Window Extended Style
  dwStyle=WS_POPUP;          // Windows Style
  ShowCursor(TRUE);         // Do not hide Mouse Pointer

  AdjustWindowRectEx(&WindowRect, // Adjust Window to fit fullscreen
                     dwStyle,
                     FALSE,
                     dwExStyle);

  hWnd=CreateWindowEx(dwExStyle, // Extended Style For The Window
                      "TorqWindow",     // Class Name
                      title.c_str(),    // Window Title
                      dwStyle |         // Defined Window Style
                      WS_CLIPSIBLINGS | // Required Window Style
                      WS_CLIPCHILDREN,  // Required Window Style
                      0, 0,             // Window Position
                      WindowRect.right, // Window Width
                      WindowRect.bottom, // Window Height
                      NULL,             // No Parent Window
                      NULL,             // No Menu
                      hInstance,        // Instance
                      NULL);            // Dont Pass Anything To WM_CREATE
  if (!hWnd) // If we didn't succeed with making a window
    return false;

  hDC=GetDC(hWnd); 
  if (!hDC) // Did We Get A Device Context?
    return false;

  // Grab a pixel format descriptor. This tells Windows what kind of buffer we want.
  // see http://msdn.microsoft.com/en-us/library/dd368826(VS.85).aspx
  static    PIXELFORMATDESCRIPTOR pfd={ // pfd Tells Windows How We Want Things To Be
    sizeof(PIXELFORMATDESCRIPTOR),    // Size Of This Pixel Format Descriptor
    1,                    // Version Number
    PFD_DRAW_TO_WINDOW |  // Format Must Support Window
    PFD_SUPPORT_OPENGL |  // Format Must Support OpenGL
    PFD_DOUBLEBUFFER,     // Must Support Double Buffering
    PFD_TYPE_RGBA,        // Request An RGBA Format
    16,                   // Select Our Color Depth
    0, 0, 0, 0, 0, 0,     // Color Bits Ignored
    0,                    // No Alpha Buffer
    0,                    // Shift Bit Ignored
    0,                    // No Accumulation Buffer
    0, 0, 0, 0,           // Accumulation Bits Ignored
    16,                   // Z-Buffer (Depth Buffer)  
    0,                    // No Stencil Buffer
    0,                    // No Auxiliary Buffer
    PFD_MAIN_PLANE,       // Main Drawing Layer
    0,                    // Reserved
    0, 0, 0               // Layer Masks Ignored
  };

  PixelFormat=ChoosePixelFormat(hDC,&pfd);
  if (!PixelFormat)
    return false;

  if (!SetPixelFormat(hDC,PixelFormat,&pfd))
    return false;

  hRC=wglCreateContext(hDC);
  if (!hRC)
    return false;

  if(!wglMakeCurrent(hDC,hRC))  // Try To Activate The Rendering Context
    return false;

  GLenum err = glewInit();
  if (GLEW_OK != err)
    return false;

  return true;
}

void destroyEngineWindow(){
  ChangeDisplaySettings(NULL,0);
  ShowCursor(TRUE);

  if (hRC){
    wglMakeCurrent(NULL,
                   NULL);
    wglDeleteContext(hRC);
    hRC=NULL;
  }

  if (hDC){
    ReleaseDC(hWnd,hDC);
    hDC=NULL;
  }

  if (hWnd){
    DestroyWindow(hWnd);
    hWnd=NULL;
  }

  UnregisterClass("TorqWindow",
                  hInstance);
  hInstance=NULL;
}

Thanks for the help!

\$\endgroup\$
7
  • 1
    \$\begingroup\$ Post the code; just because you think it's a standard loop doesn't mean you aren't doing something weird. I can think of several things offhand that might be causing this behavior. \$\endgroup\$
    – user1430
    Commented May 9, 2012 at 1:40
  • 1
    \$\begingroup\$ Also, Windows messages are plenty fine for getting input to a game; RawInput is slightly lower level but that doesn't provide you much of an advantage (and it's much harder to use). DirectInput just sits on top of one of those, depending on which version you use. It's not a huge advantage for regular keyboard/mouse input. \$\endgroup\$
    – user1430
    Commented May 9, 2012 at 1:41
  • \$\begingroup\$ There are a few obvious things that could cause this. First of all are you processing all windows messages before rendering? Second of all, is the game loop simple? Maybe you can provide some code or loop execution time (it won't say anything about OpenGL, but it'll say if the windows messages get a chance to be processed properly) \$\endgroup\$
    – dreta
    Commented May 9, 2012 at 1:42
  • \$\begingroup\$ why are you using a c++ main function under windows? \$\endgroup\$
    – dreta
    Commented May 9, 2012 at 5:10
  • \$\begingroup\$ 'Cause it's all I know? Also, I get a console without any effort. (If I read correctly, I have to create and attach my own console if I use winmain(). If you're referring to winmain. You are talking about winmain, right?) \$\endgroup\$
    – whiterook6
    Commented May 9, 2012 at 5:21

1 Answer 1

4
\$\begingroup\$

If you have VSYNC on, and if you handle windows events in the same thread where you handle rendering, you will experience some small but noticeable input lag. The SwapBuffers() function will wait for vertical sync, which slows down main loop, which again slows down recieving of windows events.

There are several different solutions.

  1. The easiest one would be just to ignore the small lag. For most small games it's not harmful.
  2. Call the render function only after some time has passed since last render call.
  3. Handle windows events in separate thread.

If you don't want to set up separate thread for that, and don't want to ignore the input lag, then the 2nd option works something like this:

double lastTime, lastRenderTime;
double desiredFps = 60;

while(!quit) {
    // Get current time and delta in milliseconds
    double currentTime = getCurrentTime();
    double delta = currentTime - lastTime;

    // Update the last time for next loop round
    lastTime = currentTime;

    handleWindowsEvents();

    update(delta);

    // Call render only when needed
    if(lastRenderTime <= currentTime - (1000.0/desiredFps)) {
        double renderDelta = currentTime - lastRenderTime;
        lastRenderTime = currentTime;
        render(renderDelta);
    }
}

You might need to tweak the if clause to be more accurate, but that's how it basically works. You can use that same method for the update function if you want to tweak how often it gets called.

Also, you might want to add in a mechanic for detecting if your render function takes too much time to be able to call it 60 times a second. In this case, you might want to change the desired FPS to half of the current desired FPS to prevent some unwanted rendering behaviour. The key is to have consistent rendering interval.

\$\endgroup\$
2
  • \$\begingroup\$ I like skipping the render method if enough time hasn't passed, but that's not quite the problem. If I set the position of some object to the position of the mouse, the actual OS mouse cursor will move ahead of the object in the scene -- by more than a frame's worth, too. \$\endgroup\$
    – whiterook6
    Commented Dec 13, 2013 at 0:04
  • \$\begingroup\$ Os updates the mouse position more often than your applications 60fps, and there is nothing you can do to prevent this other than using another thread for input. At least nothing I know of. I hide the os mouse cursor when it is over the app window, and use my own cursor to hide the small delay, to make the overall visible lag consistent and unnoticeable. \$\endgroup\$
    – Lasse
    Commented Dec 13, 2013 at 2:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .