From acb3b0b4be8c6bd2dff1aceaca69598557eea099 Mon Sep 17 00:00:00 2001 From: Frank Praznik 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 | 113 +++++++++++++++++++----- 1 file changed, 92 insertions(+), 21 deletions(-) diff --git a/src/video/windows/SDL_windowskeyboard.c b/src/video/windows/SDL_windowskeyboard.c index 560437598f..c8342c3cef 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; - 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); + // 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; } -void WIN_UpdateKeymap(bool send_event) +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); + } + + // 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; +} + +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)