From acb3b0b4be8c6bd2dff1aceaca69598557eea099 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Tue, 22 Jul 2025 12:32:46 -0400
Subject: [PATCH] win32: Implement keymap caching
Keymap construction is an expensive process, so keymaps are cached to facilitate fast switching, as they are static after initial construction, and do not need to be rebuilt every time.
---
src/video/windows/SDL_windowskeyboard.c | 111 +++++++++++++++++++-----
1 file changed, 91 insertions(+), 20 deletions(-)
diff --git a/src/video/windows/SDL_windowskeyboard.c b/src/video/windows/SDL_windowskeyboard.c
index 560437598f99f..c8342c3cef9e6 100644
--- a/src/video/windows/SDL_windowskeyboard.c
+++ b/src/video/windows/SDL_windowskeyboard.c
@@ -54,36 +54,59 @@ static void IME_SetTextInputArea(SDL_VideoData *videodata, HWND hwnd, const SDL_
#define MAPVK_VSC_TO_VK 1
#endif
-// Alphabetic scancodes for PC keyboards
-void WIN_InitKeyboard(SDL_VideoDevice *_this)
+/* Building keymaps is expensive, so keep a reasonably-sized LRU cache to
+ * enable fast switching between commonly used ones.
+ */
+static struct WIN_KeymapCache
{
-#ifndef SDL_DISABLE_WINDOWS_IME
- SDL_VideoData *data = _this->internal;
+ HKL keyboard_layout;
+ SDL_Keymap *keymap;
+} keymap_cache[4];
- data->ime_candlistindexbase = 1;
- data->ime_composition_length = 32 * sizeof(WCHAR);
- data->ime_composition = (WCHAR *)SDL_calloc(data->ime_composition_length, sizeof(WCHAR));
-#endif // !SDL_DISABLE_WINDOWS_IME
+static int keymap_cache_size;
- WIN_UpdateKeymap(false);
+static SDL_Keymap *WIN_GetCachedKeymap(HKL layout)
+{
+ SDL_Keymap *keymap = NULL;
+ for (int i = 0; i < keymap_cache_size; ++i) {
+ if (keymap_cache[i].keyboard_layout == layout) {
+ keymap = keymap_cache[i].keymap;
+
+ // Move the map to the front of the list.
+ if (i) {
+ SDL_memmove(keymap_cache + 1, keymap_cache, sizeof(struct WIN_KeymapCache) * i);
+ keymap_cache[0].keyboard_layout = layout;
+ keymap_cache[0].keymap = keymap;
+ }
+ break;
+ }
+ }
+ return keymap;
+}
- SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu");
- SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Windows");
- SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Windows");
+static void WIN_CacheKeymap(HKL layout, SDL_Keymap *keymap)
+{
+ // If the cache is full, evict the last keymap.
+ if (keymap_cache_size == SDL_arraysize(keymap_cache)) {
+ SDL_DestroyKeymap(keymap_cache[--keymap_cache_size].keymap);
+ }
- // Are system caps/num/scroll lock active? Set our state to match.
- SDL_ToggleModState(SDL_KMOD_CAPS, (GetKeyState(VK_CAPITAL) & 0x0001) ? true : false);
- SDL_ToggleModState(SDL_KMOD_NUM, (GetKeyState(VK_NUMLOCK) & 0x0001) ? true : false);
- SDL_ToggleModState(SDL_KMOD_SCROLL, (GetKeyState(VK_SCROLL) & 0x0001) ? true : false);
+ // Move all elements down by one.
+ if (keymap_cache_size) {
+ SDL_memmove(keymap_cache + 1, keymap_cache, sizeof(struct WIN_KeymapCache) * keymap_cache_size);
+ }
+
+ keymap_cache[0].keyboard_layout = layout;
+ keymap_cache[0].keymap = keymap;
+ ++keymap_cache_size;
}
-void WIN_UpdateKeymap(bool send_event)
+static SDL_Keymap *WIN_BuildKeymap()
{
SDL_Scancode scancode;
- SDL_Keymap *keymap;
BYTE keyboardState[256] = { 0 };
WCHAR buffer[16];
- SDL_Keymod mods[] = {
+ const SDL_Keymod mods[] = {
SDL_KMOD_NONE,
SDL_KMOD_SHIFT,
SDL_KMOD_CAPS,
@@ -96,7 +119,10 @@ void WIN_UpdateKeymap(bool send_event)
WIN_ResetDeadKeys();
- keymap = SDL_CreateKeymap(true);
+ SDL_Keymap *keymap = SDL_CreateKeymap(false);
+ if (!keymap) {
+ return NULL;
+ }
for (int m = 0; m < SDL_arraysize(mods); ++m) {
for (int i = 0; i < SDL_arraysize(windows_scancode_table); i++) {
@@ -160,9 +186,47 @@ void WIN_UpdateKeymap(bool send_event)
}
}
+ return keymap;
+}
+
+void WIN_UpdateKeymap(bool send_event)
+{
+ HKL layout = GetKeyboardLayout(0);
+ SDL_Keymap *keymap = WIN_GetCachedKeymap(layout);
+ if (!keymap) {
+ keymap = WIN_BuildKeymap();
+ if (keymap) {
+ WIN_CacheKeymap(layout, keymap);
+ }
+ }
+
SDL_SetKeymap(keymap, send_event);
}
+// Alphabetic scancodes for PC keyboards
+void WIN_InitKeyboard(SDL_VideoDevice *_this)
+{
+#ifndef SDL_DISABLE_WINDOWS_IME
+ SDL_VideoData *data = _this->internal;
+
+ data->ime_candlistindexbase = 1;
+ data->ime_composition_length = 32 * sizeof(WCHAR);
+ data->ime_composition = (WCHAR *)SDL_calloc(data->ime_composition_length, sizeof(WCHAR));
+#endif // !SDL_DISABLE_WINDOWS_IME
+
+ // Build and bind the current keymap.
+ WIN_UpdateKeymap(false);
+
+ SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu");
+ SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Windows");
+ SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Windows");
+
+ // Are system caps/num/scroll lock active? Set our state to match.
+ SDL_ToggleModState(SDL_KMOD_CAPS, (GetKeyState(VK_CAPITAL) & 0x0001) ? true : false);
+ SDL_ToggleModState(SDL_KMOD_NUM, (GetKeyState(VK_NUMLOCK) & 0x0001) ? true : false);
+ SDL_ToggleModState(SDL_KMOD_SCROLL, (GetKeyState(VK_SCROLL) & 0x0001) ? true : false);
+}
+
void WIN_QuitKeyboard(SDL_VideoDevice *_this)
{
#ifndef SDL_DISABLE_WINDOWS_IME
@@ -175,6 +239,13 @@ void WIN_QuitKeyboard(SDL_VideoDevice *_this)
data->ime_composition = NULL;
}
#endif // !SDL_DISABLE_WINDOWS_IME
+
+ SDL_SetKeymap(NULL, false);
+ for (int i = 0; i < keymap_cache_size; ++i) {
+ SDL_DestroyKeymap(keymap_cache[i].keymap);
+ }
+ SDL_memset(keymap_cache, 0, sizeof(keymap_cache));
+ keymap_cache_size = 0;
}
void WIN_ResetDeadKeys(void)