From a9229136eabda078c4ca657ae92c34e413b053d1 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 4 Mar 2026 15:05:29 +0100 Subject: [PATCH] Win32: Add thread data structure Add a data structure for thread-local storage. This will be useful to keep per-thread D2D interface pointers (and more) --- src/meson.build | 1 + src/win32/cairo-win32-private.h | 16 +++ src/win32/cairo-win32-system.c | 30 +++- src/win32/cairo-win32-thread-data.c | 203 ++++++++++++++++++++++++++++ 4 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 src/win32/cairo-win32-thread-data.c diff --git a/src/meson.build b/src/meson.build index ac06ac61a..f2aba13f3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -175,6 +175,7 @@ cairo_feature_sources = { 'win32/cairo-win32-surface.c', 'win32/cairo-win32-display-surface.c', 'win32/cairo-win32-printing-surface.c', + 'win32/cairo-win32-thread-data.c', ], 'cairo-win32-font': [ 'win32/cairo-win32-font.c', diff --git a/src/win32/cairo-win32-private.h b/src/win32/cairo-win32-private.h index a9eda8fdb..4b4f7f912 100644 --- a/src/win32/cairo-win32-private.h +++ b/src/win32/cairo-win32-private.h @@ -236,6 +236,22 @@ cairo_win32_get_system_text_quality (void); HMODULE _cairo_win32_load_library_from_system32 (const wchar_t *name); +typedef struct { + cairo_bool_t added_to_list; +} cairo_win32_thread_data_t; + +void +cairo_win32_thread_data_initialize (void); + +void +cairo_win32_thread_data_finalize (void); + +cairo_win32_thread_data_t * +cairo_win32_thread_data_get (void); + +void +cairo_win32_thread_data_free (void); + #if CAIRO_HAS_DWRITE_FONT cairo_int_status_t diff --git a/src/win32/cairo-win32-system.c b/src/win32/cairo-win32-system.c index c5ef24e8d..7646c4925 100644 --- a/src/win32/cairo-win32-system.c +++ b/src/win32/cairo-win32-system.c @@ -46,6 +46,8 @@ #include "cairoint.h" +#include "cairo-win32-private.h" + #include /** @@ -107,20 +109,36 @@ _cairo_win32_load_library_from_system32 (const wchar_t *name) return module_handle; } -#if CAIRO_MUTEX_IMPL_WIN32 +static void +cairo_win32_initialize (void) +{ + CAIRO_MUTEX_INITIALIZE (); + cairo_win32_thread_data_initialize (); +} + +static void +cairo_win32_finalize (void) +{ + cairo_win32_thread_data_finalize (); + CAIRO_MUTEX_FINALIZE (); +} static void NTAPI cairo_win32_tls_callback (PVOID hinstance, DWORD dwReason, PVOID lpvReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: - CAIRO_MUTEX_INITIALIZE (); + cairo_win32_initialize (); + break; + + case DLL_THREAD_DETACH: + cairo_win32_thread_data_free (); break; case DLL_PROCESS_DETACH: - if (lpvReserved == NULL) { - CAIRO_MUTEX_FINALIZE (); - } + if (lpvReserved != NULL) + break; + cairo_win32_finalize (); break; } } @@ -168,5 +186,3 @@ static const PIMAGE_TLS_CALLBACK _ptr_##func = func; #endif /* !_MSC_VER */ DEFINE_TLS_CALLBACK (cairo_win32_tls_callback); - -#endif /* CAIRO_MUTEX_IMPL_WIN32 */ diff --git a/src/win32/cairo-win32-thread-data.c b/src/win32/cairo-win32-thread-data.c new file mode 100644 index 000000000..593202d87 --- /dev/null +++ b/src/win32/cairo-win32-thread-data.c @@ -0,0 +1,203 @@ +/* cairo - a vector graphics library with display and print output + * + * Copyright © 2026 Luca Bacci + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 31 Milk Street, #960789 Boston, MA 02196, USA. + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * https://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + * The Original Code is the cairo graphics library. + * + * Contributor(s): + * Luca Bacci + */ + +#include "cairoint.h" + +#include "cairo-array-private.h" +#include "cairo-win32-private.h" + +#include +#include +#include +#include + +#if (defined (__GNUC__) && !defined (__clang__)) + /* Prefer explicit TLS for mingw-w64 GCC — EmuTLS has + * some issues and adds a dependency on libwinpthreads: + * + * https://github.com/msys2/MINGW-packages/issues/22917 + * https://github.com/msys2/MINGW-packages/issues/2519#issuecomment-304155278 + * https://gitlab.freedesktop.org/pixman/pixman/-/merge_requests/61 + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80881 + */ +#define USE_EXPLICIT_TLS +#endif + +#ifdef USE_EXPLICIT_TLS +static DWORD +tls_index = TLS_OUT_OF_INDEXES; +#else +static _Thread_local +cairo_win32_thread_data_t thread_data; +#endif + +SRWLOCK thread_data_list_lock = SRWLOCK_INIT; +cairo_array_t thread_data_list; + +/* Could use hardware_destructive_interference_size in C++17 + * or query at runtime. A modicum value works anyway, and we + * actually have init-only members in cairo_win32_thread_data + */ +const size_t CACHE_LINE_SIZE = 128; + +#ifdef USE_EXPLICIT_TLS + +static cairo_win32_thread_data_t * +thread_data_allocation_new (void) +{ + cairo_win32_thread_data_t *data; + + data = _aligned_malloc (sizeof (cairo_win32_thread_data_t), CACHE_LINE_SIZE); + assert (data != NULL); + memset (data, 0, sizeof (cairo_win32_thread_data_t)); + + return data; +} + +static void +thread_data_allocation_free (cairo_win32_thread_data_t *data) +{ + _aligned_free (data); +} + +#endif /* USE_EXPLICIT_TLS */ + +static void +thread_data_free (cairo_win32_thread_data_t *data) +{ + /* Loader-lock-safe */ + +#ifdef USE_EXPLICIT_TLS + thread_data_allocation_free (data); +#endif +} + +static cairo_win32_thread_data_t * +thread_data_retrieve (cairo_bool_t ensure_allocation) +{ +#ifdef USE_EXPLICIT_TLS + cairo_win32_thread_data_t *data = TlsGetValue (tls_index); + if (ensure_allocation && !data) { + data = thread_data_allocation_new (); + TlsSetValue (tls_index, data); + } + + return data; +#else + return &thread_data; +#endif +} + +void +cairo_win32_thread_data_initialize (void) +{ +#ifdef USE_EXPLICIT_TLS + assert(tls_index == TLS_OUT_OF_INDEXES); + + if ((tls_index = TlsAlloc ()) == TLS_OUT_OF_INDEXES) + assert (0 && "TlsAlloc failed"); +#endif + + _cairo_array_init (&thread_data_list, sizeof (cairo_win32_thread_data_t *)); +} + +void +cairo_win32_thread_data_finalize (void) +{ + for (unsigned int i = 0; i < _cairo_array_num_elements (&thread_data_list); i++) { + cairo_win32_thread_data_t **p_data = _cairo_array_index (&thread_data_list, i); + thread_data_free (*p_data); + } + + _cairo_array_fini (&thread_data_list); + +#ifdef USE_EXPLICIT_TLS + TlsFree (tls_index); +#endif +} + +cairo_win32_thread_data_t * +cairo_win32_thread_data_get (void) +{ + cairo_win32_thread_data_t *data = thread_data_retrieve (TRUE); + + if (!data->added_to_list) { + AcquireSRWLockExclusive (&thread_data_list_lock); + + data->added_to_list = TRUE; + if (_cairo_array_append (&thread_data_list, &data) != CAIRO_STATUS_SUCCESS) + abort (); + + ReleaseSRWLockExclusive (&thread_data_list_lock); + } + + return data; +} + +void +cairo_win32_thread_data_free (void) +{ + cairo_win32_thread_data_t *data = thread_data_retrieve (FALSE); + +#ifdef USE_EXPLICIT_TLS + if (!data) + return; +#endif + + if (data->added_to_list) { + cairo_win32_thread_data_t **iter; + unsigned int num_elements; + unsigned int i; + + AcquireSRWLockExclusive (&thread_data_list_lock); + + iter = (cairo_win32_thread_data_t **) _cairo_array_index (&thread_data_list, 0); + num_elements = _cairo_array_num_elements (&thread_data_list); + + for (i = 0; i < num_elements; i++) { + if (iter[i] == data) { + cairo_win32_thread_data_t *aux; + + _cairo_array_pop_element (&thread_data_list, &aux); + if (i < num_elements - 1) + iter[i] = aux; + break; + } + } + assert (i < num_elements); + + ReleaseSRWLockExclusive (&thread_data_list_lock); + } + + thread_data_free (data); +}