[PATCH v3 0/3] MR7771: win32u: Make font handle table dynamically growable.
This fixes "out of realized font handles" errors seen with Valve games like Half-Life 2 (which uses more than 1000 font handles). The new limit of 5000 is roughly inspired by the Windows limit, although there are differences. On Windows 11 I can call `CreateFont()` (and then `SetMapMode()`, `SelectObject()`, `SetTextAlign()` to realize the font) exactly 5000 times before it hits the handle limit. Under Wine, child fonts use handles as well, so the limit is usually hit before 5000 `CreateFont()` calls. But, there is caching so that `CreateFont()` calls with identical arguments do not count towards the limit. Ultimately the old limit of 256 was sufficient for almost all applications, and 5000 should be enough for all but the worst-behaved apps. -- v3: win32u: Make font handle table dynamically growable. win32u: Track free font handles through indices instead of pointers. win32u: Enter font_lock in NtGdiMakeFontDir. https://gitlab.winehq.org/wine/wine/-/merge_requests/7771
From: Brendan Shanks <bshanks(a)codeweavers.com> --- dlls/win32u/font.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dlls/win32u/font.c b/dlls/win32u/font.c index 5776c6dccc5..eedc36f8c0d 100644 --- a/dlls/win32u/font.c +++ b/dlls/win32u/font.c @@ -6249,7 +6249,8 @@ ULONG WINAPI NtGdiMakeFontDir( DWORD embed, BYTE *buffer, UINT size, const WCHAR if (path[len / sizeof(WCHAR) - 1]) return 0; if (wcslen( path ) != len / sizeof(WCHAR) - 1) return 0; - if (!(font = alloc_gdi_font( path, NULL, 0 ))) return 0; + pthread_mutex_lock( &font_lock ); + if (!(font = alloc_gdi_font( path, NULL, 0 ))) goto done; font->lf.lfHeight = 100; if (!font_funcs->load_font( font )) goto done; if (!font_funcs->set_outline_text_metrics( font )) goto done; @@ -6282,10 +6283,12 @@ ULONG WINAPI NtGdiMakeFontDir( DWORD embed, BYTE *buffer, UINT size, const WCHAR memcpy( buffer, &fontdir, sizeof(fontdir) ); free_gdi_font( font ); + pthread_mutex_unlock( &font_lock ); return fontdir.dfFace + pos; done: - free_gdi_font( font ); + if (font) free_gdi_font( font ); + pthread_mutex_unlock( &font_lock ); return 0; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7771
From: Brendan Shanks <bshanks(a)codeweavers.com> --- dlls/win32u/font.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/dlls/win32u/font.c b/dlls/win32u/font.c index eedc36f8c0d..6551984a54e 100644 --- a/dlls/win32u/font.c +++ b/dlls/win32u/font.c @@ -26,6 +26,7 @@ #include <limits.h> #include <stdarg.h> +#include <stdint.h> #include <stdlib.h> #include <string.h> #include <assert.h> @@ -2311,8 +2312,8 @@ struct font_handle_entry }; static struct font_handle_entry font_handles[MAX_FONT_HANDLES]; -static struct font_handle_entry *next_free; -static struct font_handle_entry *next_unused = font_handles; +static unsigned int next_free = UINT_MAX; +static unsigned int next_unused = 0; static struct font_handle_entry *handle_entry( unsigned int handle ) { @@ -2338,13 +2339,14 @@ static struct gdi_font *get_font_from_handle( unsigned int handle ) static DWORD alloc_font_handle( struct gdi_font *font ) { - struct font_handle_entry *entry; + struct font_handle_entry *entry = NULL; - entry = next_free; + if (next_free != UINT_MAX) + entry = &font_handles[next_free]; if (entry) - next_free = (struct font_handle_entry *)entry->font; - else if (next_unused < font_handles + MAX_FONT_HANDLES) - entry = next_unused++; + next_free = (uintptr_t)entry->font; + else if (next_unused < MAX_FONT_HANDLES) + entry = &font_handles[next_unused++]; else { ERR( "out of realized font handles\n" ); @@ -2361,8 +2363,8 @@ static void free_font_handle( DWORD handle ) if ((entry = handle_entry( handle ))) { - entry->font = (struct gdi_font *)next_free; - next_free = entry; + entry->font = (struct gdi_font *)(uintptr_t)next_free; + next_free = entry - font_handles; } } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7771
From: Brendan Shanks <bshanks(a)codeweavers.com> --- dlls/win32u/font.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/dlls/win32u/font.c b/dlls/win32u/font.c index 6551984a54e..3c50a82bae0 100644 --- a/dlls/win32u/font.c +++ b/dlls/win32u/font.c @@ -2303,7 +2303,7 @@ static struct gdi_font_face *find_matching_face( const LOGFONTW *lf, CHARSETINFO /* realized font objects */ #define FIRST_FONT_HANDLE 1 -#define MAX_FONT_HANDLES 256 +#define MAX_FONT_HANDLES 5000 struct font_handle_entry { @@ -2311,15 +2311,16 @@ struct font_handle_entry WORD generation; /* generation count for reusing handle values */ }; -static struct font_handle_entry font_handles[MAX_FONT_HANDLES]; +static struct font_handle_entry *font_handles; static unsigned int next_free = UINT_MAX; static unsigned int next_unused = 0; +static unsigned int allocated_font_handles = 256; static struct font_handle_entry *handle_entry( unsigned int handle ) { unsigned int idx = LOWORD(handle) - FIRST_FONT_HANDLE; - if (idx < MAX_FONT_HANDLES) + if (idx < allocated_font_handles) { if (!HIWORD( handle ) || HIWORD( handle ) == font_handles[idx].generation) return &font_handles[idx]; @@ -2345,12 +2346,26 @@ static DWORD alloc_font_handle( struct gdi_font *font ) entry = &font_handles[next_free]; if (entry) next_free = (uintptr_t)entry->font; - else if (next_unused < MAX_FONT_HANDLES) + else if (next_unused < allocated_font_handles) entry = &font_handles[next_unused++]; else { - ERR( "out of realized font handles\n" ); - return 0; + struct font_handle_entry *new_handles; + + if (allocated_font_handles == MAX_FONT_HANDLES) + { + ERR( "out of realized font handles\n" ); + return 0; + } + allocated_font_handles = min( allocated_font_handles * 2, MAX_FONT_HANDLES ); + new_handles = realloc( font_handles, allocated_font_handles * sizeof(*font_handles) ); + if (!new_handles) + { + ERR( "unable to grow font handle table\n" ); + return 0; + } + font_handles = new_handles; + entry = &font_handles[next_unused++]; } entry->font = font; if (++entry->generation == 0xffff) entry->generation = 1; @@ -6734,6 +6749,8 @@ UINT font_init(void) {'S','o','f','t','w','a','r','e','\\','W','i','n','e','\\','F','o','n','t','s'}; static const WCHAR cacheW[] = {'C','a','c','h','e'}; + font_handles = malloc( allocated_font_handles * sizeof(*font_handles) ); + if (!(hkcu_key = open_hkcu())) return 0; wine_fonts_key = reg_create_key( hkcu_key, wine_fonts_keyW, sizeof(wine_fonts_keyW), 0, NULL ); if (wine_fonts_key) dpi = init_font_options(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/7771
On Thu Apr 24 00:12:25 2025 +0000, Huw Davies wrote:
Right. We probably want to fix that first then. I added a commit to take `font_lock` in `NtGdiMakeFontDir`
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/7771#note_101770
This merge request was approved by Huw Davies. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/7771
If we are going to limit it to 5000 anyway, dynamic allocation doesn't seem necessary. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/7771#note_101790
participants (4)
-
Alexandre Julliard (@julliard) -
Brendan Shanks -
Brendan Shanks (@bshanks) -
Huw Davies (@huw)