Los events de Windows se retrasan

Estoy haciendo una aplicación OpenGL simple y hasta ahora puedo mostrar la window, el bucle de actualización / render funciona y procesa events de Windows (como WM_QUIT y WM_MOUSE). Sin embargo, he descubierto que hay un desfase notable entre, digamos, presionar la barra espaciadora y recibir el evento en la function WndProc (en este caso, WM_KEYDOWN). ¿Es esto resultado de usar WndProc y no de algún sistema de input directa de fantasía? ? ¿Hay una forma mejor de get, por ejemplo, la position del mouse?

Incluso puedo atar uno de mis objects para dibujar en la position actual del mouse (actualizada en esa misma function WndProc) y, aunque es muy fino, con las coorderadas adecuadas, siempre está detrás del mouse. Puedo publicar un código si están interesados, pero se trata del código de actualización / process de events más estándar que existe.

¡Gracias por la ayuda!

EDITAR: Como eres curioso, aquí está el principal ():

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(); } 

Y el WinProc, que maneja el asunto DispatchMessage (o TranslateMessage, o algo así).

 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); } 

y la creación de la window:

 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 Requinetworking 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 Cleanetworking 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 | // Requinetworking Window Style WS_CLIPCHILDREN, // Requinetworking 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 Ignonetworking 0, // No Alpha Buffer 0, // Shift Bit Ignonetworking 0, // No Accumulation Buffer 0, 0, 0, 0, // Accumulation Bits Ignonetworking 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 Ignonetworking }; 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; } 

¡Gracias por la ayuda!

Si tiene VSYNC activado, y si maneja events de Windows en el mismo subprocess donde maneja la representación, experimentará un retraso de input pequeño pero notable. La function SwapBuffers () esperará la synchronization vertical, lo que ralentiza el ciclo principal, lo que ralentiza el acceso a los events de Windows.

Hay varias soluciones diferentes.

  1. La más fácil sería simplemente ignorar el pequeño retraso. Para la mayoría de los juegos pequeños no es dañino.
  2. Llame a la function de renderizado solo después de que haya transcurrido un time desde la última llamada de renderizado.
  3. Maneje events de Windows en un hilo separado.

Si no desea configurar un hilo separado para eso y no desea ignorar el retraso de input, entonces la segunda opción funciona de la siguiente manera:

 double lastTime, lastRenderTime; double desinetworkingFps = 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/desinetworkingFps)) { double renderDelta = currentTime - lastRenderTime; lastRenderTime = currentTime; render(renderDelta); } } 

Es posible que necesite modificar la cláusula if para ser más preciso, pero así es como funciona básicamente. Puede usar el mismo método para la function de actualización si desea ajustar la frecuencia con la que se llama.

Además, es posible que desee agregar una mecánica para detectar si su function de renderización requiere demasiado time para poder llamarla 60 veces por segundo. En este caso, es posible que desee cambiar el FPS deseado a la mitad del FPS deseado actual para evitar un comportamiento de representación no deseado. La key es tener un intervalo de renderizado consistente.