0
\$\begingroup\$

I'm in the process of learning how to use the DirectX 11 API and came upon the following problem:
Although my rendering function executes quite fast at around 150 fps (as one expects considering how little geometry is rendered), the mouse and keyboard input is massivly delayed. Sometimes the "game" reacts several seconds too late or doesn't react at all; looking around is a pain.
When I increase the framerate even further to, let's say, 400 (by reducing the rendering resolution/rendering less objects), the effect vanishes and the every keystroke is detected precisely.

My update() function (see code below) is called by the StepTimer class of the DirectX Tool Kit from the main message handling loop. I tried both fixed and variable timestep mode.

Main loop calling update() and render()

while (msg.message != WM_QUIT)
{
    if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    static DX::StepTimer stepTimer;
    stepTimer.Tick([&]() { update(stepTimer); });
    render();
}

The update() function:

void update(DX::StepTimer const &timer)
{
    static Keyboard::KeyboardStateTracker keyboardTracker;
    static Mouse::ButtonStateTracker mouseTracker;
    Keyboard::State keyboardState = keyboard->GetState();
    Mouse::State mouseState = mouse->GetState();
    keyboardTracker.Update(keyboardState);
    mouseTracker.Update(mouseState);
    fps = timer.GetFramesPerSecond();

    if (keyboardState.Escape) PostQuitMessage(0);


    static float pitch = 0.0f;
    static float yaw = 0.0f;

    if (mouseState.positionMode == Mouse::MODE_RELATIVE)
    {
        pitch = std::max(std::min(pitch - float(mouseState.y) * 0.001f, XM_PI / 2.0f - 0.01f), -XM_PI / 2.0f + 0.01f);
        yaw -= float(mouseState.x) * 0.001f;
        if (yaw < -XM_PI) yaw += XM_PI * 2.0f;
        if (yaw > XM_PI) yaw -= XM_PI * 2.0f;
    }
    if(mouseTracker.leftButton == Mouse::ButtonStateTracker::RELEASED) mouse->SetMode(mouseState.positionMode == Mouse::MODE_ABSOLUTE ? Mouse::MODE_RELATIVE : Mouse::MODE_ABSOLUTE);

    Vector3 cameraDir(-sin(yaw) * cos(pitch), sin(pitch), cos(yaw) * cos(pitch));


    Vector3 motionVec = { 0.0f, 0.0f, 0.0f };
    float speed = 4.2f;
    if (keyboardState.W || keyboardState.Up)
    {
        motionVec.x += cameraDir.x;
        motionVec.z += cameraDir.z;
    }
    if (keyboardState.S || keyboardState.Down)
    {
        motionVec.x -= cameraDir.x;
        motionVec.z -= cameraDir.z;
    }
    if (keyboardState.A || keyboardState.Left)
    {
        motionVec.x -= cameraDir.z;
        motionVec.z += cameraDir.x;
    }
    if (keyboardState.D || keyboardState.Right)
    {
        motionVec.x += cameraDir.z;
        motionVec.z -= cameraDir.x;
    }
    if (keyboardState.Space || keyboardState.PageUp)
    {
        motionVec.y += 1.0f;
    }
    if (keyboardState.LeftShift || keyboardState.PageDown)
    {
        motionVec.y -= 1.0f;
    }
    motionVec *= speed;

    static Vector3 cameraPos = { 0.0f, 2.0f, 0.0f };
    if (keyboardState.R)
    {
        cameraPos = { 0.0f, 2.0f, 0.0f };
        pitch = 0.0f;
        yaw = 0.0f;
    }
    cameraPos += motionVec * float(timer.GetElapsedSeconds());
    matrixCamera = XMMatrixLookToLH(cameraPos.XMVECTOR(), cameraDir.XMVECTOR(), { 0.0f, 1.0f, 0.0f });
}

Is there any hint you can give me? Or might this even by a bug in the StepTimer class?

\$\endgroup\$
  • \$\begingroup\$ Generally you should instance your StepTimer as a global or as a member of a global singleton class, not as a local static variable. See this blog post and the wiki for additional documentation for StepTimer. BTW, you should do the same with your mouse & keyboard button tracker objects. \$\endgroup\$ – Chuck Walbourn Jan 5 at 19:26
  • \$\begingroup\$ I read those pages you linked yesterday, actually those are the resources I built my code on. I couldn't find find a reason to make those objects global so I decided to keep them local for now. What are the reasons against this decision? \$\endgroup\$ – K. Krull Jan 5 at 19:48
0
\$\begingroup\$

Your main Win32 message pump is only ever processing a single Win32 message per frame. You need to empty the message queue between each render frame since there are usually dozens or more messages queued up in a single frame time.

Here's a common pattern. Notice that here I'm only doing the tick/update/render cycle if there is no Win32 message to process:

while (WM_QUIT != msg.message)
{
    if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else // <---- This is the key part you are missing.
    {
        stepTimer.Tick([&]() { update(stepTimer); });
        render();
    }
}

See GitHub for complete basic Direct3D render loops for various Microsoft platforms.

Note that StepTimer is not my invention or strictly a part of the DirectX Tool Kit. It was created by Shawn Hargreaves (MSFT) for the Visual Studio DirectX templates for Windows Store 8.1 to replace the somewhat less robust BasicTimer class that was shipped in the Visual Studio DirectX templates for Windows Store 8.0. As Shawn was one of the original engineers on XNA Game Studio, that team really deserves the credit as the creators of the StepTimer design as talked about on his blog.

I have made sure that StepTimer has gotten more adoption and use in various other templates, samples, etc. and I use it for the DirectX Tool Kit tutorials extensively per my blog post.

\$\endgroup\$
  • \$\begingroup\$ While writing this comment, I see you edited the answer and came to the same conclusion I came to while experimenting with the original version. Now it perfectly describes the solution to my problem! However, another effect appeared that might be somehow related: The mouse sensitivity decreases with decreasing framerate. Why is this? \$\endgroup\$ – K. Krull Jan 5 at 19:52
  • \$\begingroup\$ Are you drawing the mouse cursor or are you using the system cursor? \$\endgroup\$ – Chuck Walbourn Jan 6 at 20:27
  • \$\begingroup\$ I'm using the system cursor (which works fine), but once the user presses the mouse button, the cursor vanishes and the mouse input is used to control the camera (here the problem occurs). The code used to achieve this is inside the update function. \$\endgroup\$ – K. Krull Jan 6 at 21:47
  • \$\begingroup\$ That's a different question, but the problem is likely that you don't have any term in your update logic that includes the elapsed time which would tie it to the frame rate. \$\endgroup\$ – Chuck Walbourn Jan 6 at 21:55
  • \$\begingroup\$ Okay then. I'll try to figure it out myself and start a new question if necessary. Thanks for the help! For everyone else reading this: Do not use my code, you will most likely run into serious trouble. Rather have a look at the examples provided in the answer. \$\endgroup\$ – K. Krull Jan 6 at 22:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.