3.8.5: Keyboard.GetState() keeps keys "down" for several frames after release on DX12 / DesktopGL / DesktopVK (WindowsDX is fine)

*The English text is a translation of the Japanese.

[EN]

I wasn’t sure whether to open a GitHub issue, so I am posting this here first.

In MonoGame 3.8.5, it appears that Keyboard.GetState() continues to report a key as pressed for a short period (several frames) even after the key has been physically released. This is particularly noticeable when releasing keys after holding down multiple keys—such as “Left” and “Up”—simultaneously for a moment. Because IsKeyDown remains true briefly, objects like sprites controlled by keyboard input continue to move even after the keys are released.

Here is what I found while narrowing down the cause:

  • The issue reproduces even in a minimal Game configuration (calling only Keyboard.GetState() within Update() and using no third-party code).
  • I compared GetState() with the Win32 GetAsyncKeyState() (which reflects the immediate physical state) within the same frame: GetAsyncKeyState switches to the “up” (released) state the moment the key is released, whereas GetState() remains in the “down” (pressed) state for several frames afterward.
  • Differences by backend:
    • WindowsDX (Legacy) → Normal (switches to released state immediately)
    • WindowsDX12 → Remains in pressed state after release
    • DesktopGL (OpenGL) → Remains in pressed state after release
    • DesktopVK (Vulkan) → Remains in pressed state after release

In short, this appears to be an issue specific to the new SDL2-based backends introduced in version 3.8.5. Reproduction code (includes a minimal app and Windows-specific comparison code):

[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern short GetAsyncKeyState(int vKey);

protected override void Update(GameTime gameTime)
{
    var ks = Keyboard.GetState();
    bool getState  = ks.IsKeyDown(Keys.Left);
    bool physical   = (GetAsyncKeyState(0x25) & 0x8000) != 0; // VK_LEFT
    Window.Title = $"GetState Left={getState}   GetAsyncKeyState Left={physical}";
    base.Update(gameTime);
}

Reproduction steps: Focus the window, hold down the Left and Up keys for a few seconds, and then release them simultaneously.
Expected behavior: Both should immediately return false. Actual behavior: GetAsyncKeyState immediately returns false, but GetState remains true for several frames.

Environment: MonoGame 3.8.5, Windows 11, .NET 10.

Is this a known issue with the new backend’s input handling? Or am I overlooking something (such as a setting)?

[JP]

GitHub の Issue を登録するかどうか判断に迷いましたので一旦こちらに投稿いたします。

MonoGame 3.8.5 において、キーを物理的に離した後も、Keyboard.GetState() がしばらく(数フレーム)の間、キーが押されたままの状態で取得されるようです。特に、「左」+「上」などの複数キーを少しの間押し続けてから離した際に顕著です。IsKeyDown が短時間 true のままになるため、キー入力で動くスプライトなどが、キーを離した後も動き続けてしまいます。

原因を絞り込む過程で分かったことは以下の通りです:

  • 最低限の構成の Game(Update() 内で Keyboard.GetState() を呼ぶのみ、サードパーティ製コードなし)でも再現します。
  • 同一フレーム内で GetState() と Win32 の GetAsyncKeyState()(物理的な即時状態)を比較しました:GetAsyncKeyState は離した瞬間に「離された(up)」状態に切り替わりますが、GetState() はその後も数フレーム間「押下(down)」状態のままです。
  • バックエンドによる違い:
    • WindowsDX (従来型) → 正常(即座に離された状態になる)
    • WindowsDX12 → 離した後も押下状態が続く
    • DesktopGL (OpenGL) → 離した後も押下状態が続く
    • DesktopVK (Vulkan) → 離した後も押下状態が続く

つまり、3.8.5 で導入された新しい SDL2 ベースのバックエンドに特有の問題のようです。

再現用コード(最小構成のアプリ、Windows 限定の比較用コードを含む):

[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern short GetAsyncKeyState(int vKey);

protected override void Update(GameTime gameTime)
{
    var ks = Keyboard.GetState();
    bool getState  = ks.IsKeyDown(Keys.Left);
    bool physical   = (GetAsyncKeyState(0x25) & 0x8000) != 0; // VK_LEFT
    Window.Title = $"GetState Left={getState}   GetAsyncKeyState Left={physical}";
    base.Update(gameTime);
}

再現手順:ウィンドウにフォーカスを合わせ、「左」キー「上」キーを数秒間押し続け、同時に離す。
期待される動作:両方とも即座に false になること。実際の挙動:GetAsyncKeyState は即座に false になりますが、GetState は数フレームの間 true のままとなります。

環境:MonoGame 3.8.5、Windows 11、.NET 10。

これは新しいバックエンドの入力処理における既知の問題でしょうか?それとも、何か(設定など)を見落としているのでしょうか?