WM_INPUT polling rate

Started by
17 comments, last by tanzanite7 11 years, 11 months ago
I'm playing around with WM_INPUT, but am getting an issue where I can't drag or resize my window on my desktop computer (it's fine in a VM or on my laptop). It looks as if my app gets flooded with WM_INPUT messages for a while after I move my mouse, and then stops, and during that flood I can't drag/resize and I get the Windows loading spinner icon. Setting the polling rate of my Logitech G5 in SetPoint to 1000Hz makes it worse; setting it to 125Hz isn't too bad but it's still there. Note: I tried my mouse on my laptop too, but since I don't have SetPoint installed on it, it doesn't exhibit this behaviour.

Should I be handling WM_INPUT separately to the rest of my app? I've got some basic code which shows this problem when you have a high-frequency mouse:


#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <stdio.h>
#include "console.h"

HWND hWnd;
HDC hDC;
HGLRC hRC;

// Set up pixel format for graphics initialization
void SetupPixelFormat()
{
PIXELFORMATDESCRIPTOR pfd, *ppfd;
int pixelformat;
pfd = &pfd;
ppfd->nSize = sizeof(PIXELFORMATDESCRIPTOR);
ppfd->nVersion = 1;
ppfd->dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
ppfd->dwLayerMask = PFD_MAIN_PLANE;
ppfd->iPixelType = PFD_TYPE_COLORINDEX;
ppfd->cColorBits = 16;
ppfd->cDepthBits = 16;
ppfd->cAccumBits = 0;
ppfd->cStencilBits = 0;
pixelformat = ChoosePixelFormat(hDC, ppfd);
SetPixelFormat(hDC, pixelformat, ppfd);
}

// Initialize OpenGL graphics
void InitGraphics()
{
hDC = GetDC(hWnd);
SetupPixelFormat();
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hRC);
glClearColor(0, 0, 0, 0.5);
glClearDepth(1.0);
glEnable(GL_DEPTH_TEST);
}

// Resize graphics to fit window
void ResizeGraphics()
{
// Get new window size
RECT rect;
int width, height;
GLfloat aspect;
GetClientRect(hWnd, &rect);
width = rect.right;
height = rect.bottom;
aspect = (GLfloat)width / height;
// Adjust graphics to window size
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, aspect, 1.0, 100.0);
glMatrixMode(GL_MODELVIEW);
}

// Draw frame
void DrawGraphics()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Set location in front of camera
glLoadIdentity();
glTranslated(0, 0, -10);
// Draw a square
glBegin(GL_QUADS);
glColor3d(1, 0, 0);
glVertex3d(-2, 2, 0);
glVertex3d(2, 2, 0);
glVertex3d(2, -2, 0);
glVertex3d(-2, -2, 0);
glEnd();
// Show the new scene
SwapBuffers(hDC);
}

// Handle window events and messages
LONG WINAPI MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_MOUSEMOVE:
//printf("\nMouse Move message received\n");
return 0;
break;

case WM_INPUT:
// printf("\nInput message received %d", rand());
return 0;
break;

case WM_SIZE:
ResizeGraphics();
break;

case WM_CLOSE:
DestroyWindow(hWnd);
break;

case WM_DESTROY:
PostQuitMessage(0);
break;

// Default event handler
default:
return DefWindowProc (hWnd, uMsg, wParam, lParam);
break;
}

return 1;
}

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
SetStdOutToNewConsole();
const LPCWSTR appname = TEXT("OpenGL Sample");
WNDCLASS wndclass;
MSG msg;

// Define the window class
wndclass.style = 0;
wndclass.lpfnWndProc = (WNDPROC)MainWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(hInstance, appname);
wndclass.hCursor = LoadCursor(NULL,IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wndclass.lpszMenuName = appname;
wndclass.lpszClassName = appname;

// Register the window class
if (!RegisterClass(&wndclass)) return FALSE;

//Calculate x and y coords of where window should be
int dwWidth = GetSystemMetrics(SM_CXSCREEN);
int dwHeight = GetSystemMetrics(SM_CYSCREEN);
dwWidth /= 2;
dwWidth -= (800/2);
dwHeight /= 2;
dwHeight -= (600/2);
// Create the window
hWnd = CreateWindow(
appname,
appname,
WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
dwWidth,
dwHeight,
800,
600,
NULL,
NULL,
hInstance,
NULL);

if (!hWnd) return FALSE;

RAWINPUTDEVICE Rid[1];
Rid[0].usUsagePage = 0x01;
Rid[0].usUsage = 0x02;
Rid[0].dwFlags = RIDEV_INPUTSINK;
Rid[0].hwndTarget = hWnd;
RegisterRawInputDevices(Rid, 1, sizeof(Rid[0]));

// Initialize OpenGL
InitGraphics();

// Display the window
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

// Event loop
while (1)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) == TRUE)
{
if (!GetMessage(&msg, NULL, 0, 0)) return TRUE;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
DrawGraphics();
}
wglDeleteContext(hRC);
ReleaseDC(hWnd, hDC);
}
Advertisement
printf() is a pretty heavy-duty thing to do in an input message handler. If you replace that call with something simpler, like incrementing a counter, does the problem continue?

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Yep, tried commenting the printf() statements out and still get the same behaviour.
You should also comment out the printf on your WM_MOUSEMOVE handler, as that is likely getting called too.

Check the documentation for WM_INPUT (and any other message you handle) as well - the normal convention is to return 0 from your WndProc if you handle them yourself (a quick check of WM_INPUT confirms this - "If an application processes this message, it should return zero"), but you're returning 1. Beware though as there are some messages that require you to return non-zero, so be certain that you assume nothing here.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

Made those changes (commented both printf statements, and returned 0 after both WM_MOUSEMOVE and WM_INPUT) but the problem persists. Just to note, when printing statements, on the laptop when moving the mouse I'd generally get 2-3 WM_INPUT messages then a WM_MOUSEMOVE. On my desktop I get a huge amount of WM_INPUTS with a rare WM_MOUSEMOVE inbetween.
Could just be that your drivers are doing something evil; you mentioned the polling rate affects this. Does using that mouse on other computers replicate the problem?

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Figured it out, it's because I was using an if statement with PeekMessage rather than a while loop so only one message was getting processed per game loop (I should have put this in the Game Programming section, and should have also read up on the Win32 message system, sorry!) I guess the polling rate on the other computers was set too low even though I used the same mouse.

This post helped:

http://www.gamedev.n...vs-peekmessage/

I have another question: I see there being 2 ways of capturing input - either you can call a function every game loop iteration which checks the current keyboard/mouse button/mouse pointer inputs, or you can use the message system, receive messages independently to your main game loop, then process what you've captured every game loop iteration. What is the preferred way of doing this? Bear in mind I'll be using raw mouse input for accuracy, so would it be better to go with the latter, buffer the raw data and then during each game loop iteration just combine all the data in the buffer into a single set of values that can be used in my game?
I tend to prefer buffered messages over polling, mainly because it makes it harder to "miss" inputs that occur between ticks when you're busy doing other things.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

The way I understand it is you can:

- Use WM_INPUT to receive every single keyboard/mouse event individually, where you'd get the data for each event using GetRawInputData, update your own key array/mouse pointer structs every time you get that message, then when it comes to your game loop use the latest data, OR

- You poll it manually every game loop using GetRawInputBuffer, which gives you a buffer of inputs, which you'd process in one go, update your own key array/mouse pointer structs and use in that game loop.

So either way there shouldn't be any missed inputs, in fact the second method sounds nicer to me as there would be less function calls and messages being passed to WndProc, but I may be missing something so am wondering which method is generally used, and is "nicer" :)
The preferred way is as suggested by ApochPiQ. You must, and I repeat absolutely must use events to handle input. Have you ever played a game that forgot to fire even though you clearly hit the fire button? Only via polling can this happen. And it is extremely irritating to any human when it happens.
Listening to events is the only way to ensure this never happens.
And only by listening to events on a second thread is this guaranteed to work as promised. Smooth mouse movements require this, and there is some work involved in making it happen correctly, but your players will appreciate it.
In either case, even if you do not use a second thread for game logic and rendering, you still must absolutely listen to events and base your input functionality off those timings.

My frame took 2 seconds to draw. Did the player input last for 2 seconds or did it last for only 0.025 seconds? He or she gently tapped the Up direction, and certainly not for 2 seconds. This is a fairly important question to answer, and makes a huge world of a difference to the player.

Polling is absolutely unacceptable in the world of game development. But simple event-handling is not either. The solution you seek requires a lot more complexity.
Every input event should have a timestamp and some kind of input manager (one for mouse, one for touch, one for keyboard) should be able to present this data in the way the player originally intended it. With proper spacing between events etc.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

This topic is closed to new replies.

Advertisement