From 510a38b3862888a03254c53ae3972cb28ebdaf36 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 8 Apr 2026 15:49:55 +0200 Subject: [PATCH 01/12] Atomics: Add _cairo_atomic_init_once_check The new function check if the protected resource was initialized or not. It's meant for cleanup on explicit module unload (dlclose or FreeLibrary), where there can't be concurrent code using the module anymore --- src/cairo-atomic-private.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/cairo-atomic-private.h b/src/cairo-atomic-private.h index d8d805771..438292170 100644 --- a/src/cairo-atomic-private.h +++ b/src/cairo-atomic-private.h @@ -514,6 +514,19 @@ _cairo_atomic_init_once_leave(cairo_atomic_once_t *once) } } +static cairo_always_inline cairo_bool_t +_cairo_atomic_init_once_check(cairo_atomic_once_t *once) +{ + BOOL pending; + + if (InitOnceBeginInitialize (once, INIT_ONCE_CHECK_ONLY, &pending, NULL)) { + assert (!pending); + return TRUE; + } + + return FALSE; +} + #else typedef cairo_atomic_int_t cairo_atomic_once_t; @@ -547,6 +560,19 @@ _cairo_atomic_init_once_leave(cairo_atomic_once_t *once) assert (0 && "incorrect use of _cairo_atomic_init_once API (once != CAIRO_ATOMIC_ONCE_INITIALIZING)"); } +static cairo_always_inline cairo_bool_t +_cairo_atomic_init_once_check(cairo_atomic_once_t *once) +{ + int val = _cairo_atomic_int_get(once); + + if (unlikely(val == CAIRO_ATOMIC_ONCE_INITIALIZING)) + assert (0 && "incorrect use of _cairo_atomic_init_check API (once == CAIRO_ATOMIC_ONCE_INITIALIZING)"); + + assert (val == CAIRO_ATOMIC_ONCE_UNINITIALIZED || val == CAIRO_ATOMIC_ONCE_INITIALIZED); + + return val == CAIRO_ATOMIC_ONCE_INITIALIZED; +} + #endif /* !_WIN32 */ CAIRO_END_DECLS From a9229136eabda078c4ca657ae92c34e413b053d1 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 4 Mar 2026 15:05:29 +0100 Subject: [PATCH 02/12] 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); +} From 1f5230dde0a9c5663511397476aa378860bbd52b Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 4 Mar 2026 15:10:13 +0100 Subject: [PATCH 03/12] GDI: Use thread data for font HDC Use the newly-introduced thread data structure to store the per-thread font HDC. As an added bonus, we can now avoid leaking HDCs on module unlaods. --- src/win32/cairo-win32-font.c | 32 ++++++++++++++--------------- src/win32/cairo-win32-private.h | 14 +++++++++++-- src/win32/cairo-win32-system.c | 6 ++++++ src/win32/cairo-win32-thread-data.c | 10 +++++++++ 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/win32/cairo-win32-font.c b/src/win32/cairo-win32-font.c index 21c19a141..d57d86b4e 100644 --- a/src/win32/cairo-win32-font.c +++ b/src/win32/cairo-win32-font.c @@ -143,19 +143,13 @@ _cairo_win32_scaled_font_init_glyph_path (cairo_win32_scaled_font_t *scaled_font static HDC _get_global_font_dc (void) { - static cairo_atomic_once_t once = CAIRO_ATOMIC_ONCE_INIT; - static DWORD hdc_tls_index; - HDC hdc; + cairo_win32_thread_data_t *data = cairo_win32_thread_data_get (); - if (_cairo_atomic_init_once_enter (&once)) { - hdc_tls_index = TlsAlloc (); - assert (hdc_tls_index != TLS_OUT_OF_INDEXES); - _cairo_atomic_init_once_leave (&once); - } + if (!data->hdc) { + HDC hdc_screen = GetDC (NULL); + HDC hdc; - hdc = TlsGetValue (hdc_tls_index); - if (!hdc) { - hdc = CreateCompatibleDC (NULL); + hdc = CreateCompatibleDC (hdc_screen); if (!hdc) { fprintf (stderr, "%s:%s\n", __FUNCTION__, "CreateCompatibleDC"); return NULL; @@ -167,13 +161,19 @@ _get_global_font_dc (void) return NULL; } - if (!TlsSetValue (hdc_tls_index, hdc)) { - DeleteDC (hdc); - return NULL; - } + data->hdc = hdc; + /* From MSDN docs for CreateCompatibleDC: + * + * If [the reference] hdc is NULL, the thread that calls CreateCompatibleDC + * owns the HDC that is created. When this thread is destroyed, the HDC is + * no longer valid. + */ + data->free_hdc = (hdc_screen != NULL); + + ReleaseDC (NULL, hdc_screen); } - return hdc; + return data->hdc; } static cairo_status_t diff --git a/src/win32/cairo-win32-private.h b/src/win32/cairo-win32-private.h index 4b4f7f912..410a95aa8 100644 --- a/src/win32/cairo-win32-private.h +++ b/src/win32/cairo-win32-private.h @@ -36,14 +36,16 @@ #ifndef CAIRO_WIN32_PRIVATE_H #define CAIRO_WIN32_PRIVATE_H -#include "cairo-win32.h" - #include "cairoint.h" #include "cairo-device-private.h" #include "cairo-surface-clipper-private.h" #include "cairo-surface-private.h" +#include "cairo-win32.h" + +#include + #define WIN32_FONT_LOGICAL_SCALE 32 CAIRO_BEGIN_DECLS @@ -236,7 +238,15 @@ cairo_win32_get_system_text_quality (void); HMODULE _cairo_win32_load_library_from_system32 (const wchar_t *name); +typedef DWORD (__stdcall *stdcall_free_func_t) (void *); + +void +cairo_win32_async_stdcall_free (stdcall_free_func_t func, void *data); + typedef struct { + HDC hdc; + cairo_bool_t free_hdc; + cairo_bool_t added_to_list; } cairo_win32_thread_data_t; diff --git a/src/win32/cairo-win32-system.c b/src/win32/cairo-win32-system.c index 7646c4925..ce7ece96f 100644 --- a/src/win32/cairo-win32-system.c +++ b/src/win32/cairo-win32-system.c @@ -109,6 +109,12 @@ _cairo_win32_load_library_from_system32 (const wchar_t *name) return module_handle; } +void +cairo_win32_async_stdcall_free (stdcall_free_func_t func, void *data) +{ + QueueUserWorkItem (func, data, WT_EXECUTEDEFAULT); +} + static void cairo_win32_initialize (void) { diff --git a/src/win32/cairo-win32-thread-data.c b/src/win32/cairo-win32-thread-data.c index 593202d87..169f6b9ce 100644 --- a/src/win32/cairo-win32-thread-data.c +++ b/src/win32/cairo-win32-thread-data.c @@ -97,6 +97,16 @@ thread_data_free (cairo_win32_thread_data_t *data) { /* Loader-lock-safe */ + if (data->free_hdc) { + /* Delete the HDC explicitly only if it was created via a non-NULL + * reference HDC. Otherwise the system deletes it automatically on + * thread-exit and our asynchronous delete would be racy. For more + * informations, refer to the MSDN docs for CreateCompatibleDC. + */ + void *free_func = DeleteDC; + cairo_win32_async_stdcall_free (free_func, data->hdc); + } + #ifdef USE_EXPLICIT_TLS thread_data_allocation_free (data); #endif From 7fa1a901eacd86c80fde45cd1eb4ac80cf68e9e5 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 4 Mar 2026 15:18:29 +0100 Subject: [PATCH 04/12] GDI: Rename _get_global_font_dc to _get_thread_font_dc The HDC is thread-local since a long time, make this clear by renaming the function. --- src/win32/cairo-win32-font.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/win32/cairo-win32-font.c b/src/win32/cairo-win32-font.c index d57d86b4e..67c09d14e 100644 --- a/src/win32/cairo-win32-font.c +++ b/src/win32/cairo-win32-font.c @@ -141,7 +141,7 @@ _cairo_win32_scaled_font_init_glyph_path (cairo_win32_scaled_font_t *scaled_font #define NEARLY_ZERO(d) (fabs(d) < (1. / 65536.)) static HDC -_get_global_font_dc (void) +_get_thread_font_dc (void) { cairo_win32_thread_data_t *data = cairo_win32_thread_data_get (); @@ -314,7 +314,7 @@ _win32_scaled_font_create (LOGFONTW *logfont, cairo_matrix_t scale; cairo_status_t status; - hdc = _get_global_font_dc (); + hdc = _get_thread_font_dc (); if (hdc == NULL) return _cairo_error (CAIRO_STATUS_NO_MEMORY); @@ -654,7 +654,7 @@ _cairo_win32_scaled_font_ucs4_to_index (void *abstract_font, HDC hdc = NULL; cairo_status_t status; - hdc = _get_global_font_dc (); + hdc = _get_thread_font_dc (); assert (hdc != NULL); status = cairo_win32_scaled_font_select_font (&scaled_font->base, hdc); @@ -682,7 +682,7 @@ _cairo_win32_scaled_font_set_metrics (cairo_win32_scaled_font_t *scaled_font) TEXTMETRIC metrics = {0}; HDC hdc; - hdc = _get_global_font_dc (); + hdc = _get_thread_font_dc (); assert (hdc != NULL); if (scaled_font->preserve_axes || scaled_font->base.options.hint_metrics == CAIRO_HINT_METRICS_OFF) { @@ -764,7 +764,7 @@ _cairo_win32_scaled_font_init_glyph_metrics (cairo_win32_scaled_font_t *scaled_f cairo_text_extents_t extents; HDC hdc; - hdc = _get_global_font_dc (); + hdc = _get_thread_font_dc (); assert (hdc != NULL); if (scaled_font->is_bitmap) { @@ -899,7 +899,7 @@ _cairo_win32_scaled_font_glyph_bbox (void *abstract_font, cairo_status_t status; int i; - hdc = _get_global_font_dc (); + hdc = _get_thread_font_dc (); assert (hdc != NULL); status = cairo_win32_scaled_font_select_font (&scaled_font->base, hdc); @@ -1143,7 +1143,7 @@ _cairo_win32_scaled_font_load_truetype_table (void *abstract_font, cairo_status_t status; DWORD ret; - hdc = _get_global_font_dc (); + hdc = _get_thread_font_dc (); assert (hdc != NULL); tag = (tag&0x000000ffu)<<24 | (tag&0x0000ff00)<<8 | (tag&0x00ff0000)>>8 | (tag&0xff000000)>>24; @@ -1176,7 +1176,7 @@ _cairo_win32_scaled_font_index_to_ucs4 (void *abstract_font, unsigned int i, j, num_glyphs; cairo_status_t status; - hdc = _get_global_font_dc (); + hdc = _get_thread_font_dc (); assert (hdc != NULL); status = cairo_win32_scaled_font_select_font (&scaled_font->base, hdc); @@ -1471,7 +1471,7 @@ _cairo_win32_scaled_font_init_glyph_path (cairo_win32_scaled_font_t *scaled_font if (scaled_font->is_bitmap) return CAIRO_INT_STATUS_UNSUPPORTED; - hdc = _get_global_font_dc (); + hdc = _get_thread_font_dc (); assert (hdc != NULL); path = _cairo_path_fixed_create (); From 42d5c42297b86d3efeb5fac5959d52e0ecb3ee41 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 4 Mar 2026 15:23:12 +0100 Subject: [PATCH 05/12] DWriteFactory: Remove unused method and member object Remove code that has never been used --- src/win32/cairo-dwrite-font.cpp | 91 +-------------------------------- 1 file changed, 1 insertion(+), 90 deletions(-) diff --git a/src/win32/cairo-dwrite-font.cpp b/src/win32/cairo-dwrite-font.cpp index aca908723..98c61075c 100644 --- a/src/win32/cairo-dwrite-font.cpp +++ b/src/win32/cairo-dwrite-font.cpp @@ -166,33 +166,9 @@ public: return mFactoryInstance4; } - static RefPtr RenderTarget() - { - if (!mRenderTarget) { - if (!Instance()) { - return NULL; - } - // Create a DC render target. - D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( - D2D1_RENDER_TARGET_TYPE_DEFAULT, - D2D1::PixelFormat( - DXGI_FORMAT_B8G8R8A8_UNORM, - D2D1_ALPHA_MODE_PREMULTIPLIED), - 0, - 0, - D2D1_RENDER_TARGET_USAGE_NONE, - D2D1_FEATURE_LEVEL_DEFAULT - ); - - Instance()->CreateDCRenderTarget(&props, &mRenderTarget); - } - return mRenderTarget; - } - private: static RefPtr mFactoryInstance; static RefPtr mFactoryInstance4; - static RefPtr mRenderTarget; }; class WICImagingFactory @@ -226,7 +202,6 @@ RefPtr DWriteFactory::mSystemCollection; RefPtr DWriteFactory::mDefaultRenderingParams; RefPtr D2DFactory::mFactoryInstance; -RefPtr D2DFactory::mRenderTarget; static RefPtr _create_rendering_params(IDWriteRenderingParams *params, @@ -2197,52 +2172,6 @@ _dwrite_draw_glyphs_to_gdi_surface_gdi(cairo_win32_surface_t *surface, return CAIRO_INT_STATUS_SUCCESS; } -cairo_int_status_t -_dwrite_draw_glyphs_to_gdi_surface_d2d(cairo_win32_surface_t *surface, - DWRITE_MATRIX *transform, - DWRITE_GLYPH_RUN *run, - COLORREF color, - const RECT &area) -{ - HRESULT hr; - - RefPtr rt = D2DFactory::RenderTarget(); - - // XXX don't we need to set RenderingParams on this RenderTarget? - - hr = rt->BindDC(surface->dc, &area); - if (FAILED(hr)) - return CAIRO_INT_STATUS_UNSUPPORTED; - - // D2D uses 0x00RRGGBB not 0x00BBGGRR like COLORREF. - color = (color & 0xFF) << 16 | - (color & 0xFF00) | - (color & 0xFF0000) >> 16; - RefPtr brush; - hr = rt->CreateSolidColorBrush(D2D1::ColorF(color, 1.0), &brush); - if (FAILED(hr)) - return CAIRO_INT_STATUS_UNSUPPORTED; - - if (transform) { - rt->SetTransform(D2D1::Matrix3x2F(transform->m11, - transform->m12, - transform->m21, - transform->m22, - transform->dx, - transform->dy)); - } - rt->BeginDraw(); - rt->DrawGlyphRun(D2D1::Point2F(0, 0), run, brush); - hr = rt->EndDraw(); - if (transform) { - rt->SetTransform(D2D1::Matrix3x2F::Identity()); - } - if (FAILED(hr)) - return CAIRO_INT_STATUS_UNSUPPORTED; - - return CAIRO_INT_STATUS_SUCCESS; -} - /* Surface helper function */ cairo_int_status_t _cairo_dwrite_show_glyphs_on_surface(void *surface, @@ -2316,25 +2245,7 @@ _cairo_dwrite_show_glyphs_on_surface(void *surface, RECT copyArea, dstArea = { 0, 0, dst->extents.width, dst->extents.height }; IntersectRect(©Area, &fontArea, &dstArea); -#ifdef CAIRO_TRY_D2D_TO_GDI - status = _dwrite_draw_glyphs_to_gdi_surface_d2d(dst, - mat, - &run, - color, - copyArea); - - if (status == (cairo_status_t)CAIRO_INT_STATUS_UNSUPPORTED) { -#endif - status = _dwrite_draw_glyphs_to_gdi_surface_gdi(dst, - mat, - &run, - color, - dwritesf, - copyArea); - -#ifdef CAIRO_TRY_D2D_TO_GDI - } -#endif + status = _dwrite_draw_glyphs_to_gdi_surface_gdi(dst, mat, &run, color, dwritesf, copyArea); return status; } From 098ebb9c4e6b2780f4c00edcb4b568e79b63e677 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 4 Mar 2026 15:32:00 +0100 Subject: [PATCH 06/12] D2DFactory: Remove unused method and member object This is likely an innocent copy-paste error, you can't get a DWrite factory from a D2D1 factory. --- src/win32/cairo-dwrite-font.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/win32/cairo-dwrite-font.cpp b/src/win32/cairo-dwrite-font.cpp index 98c61075c..1fcf4691f 100644 --- a/src/win32/cairo-dwrite-font.cpp +++ b/src/win32/cairo-dwrite-font.cpp @@ -156,19 +156,8 @@ public: return mFactoryInstance; } - static RefPtr Instance4() - { - if (!mFactoryInstance4) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance4); - } - } - return mFactoryInstance4; - } - private: static RefPtr mFactoryInstance; - static RefPtr mFactoryInstance4; }; class WICImagingFactory From 6bb69c6488853db677e49329733263e91eb709c5 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 4 Mar 2026 16:23:28 +0100 Subject: [PATCH 07/12] DWriteFactory: Make thread-safe Use global variables but protect initialization via cairo_init_once functions. --- src/win32/cairo-dwrite-font.cpp | 17 +++- src/win32/cairo-dwrite-private.hpp | 143 ++++++++++++++--------------- 2 files changed, 81 insertions(+), 79 deletions(-) diff --git a/src/win32/cairo-dwrite-font.cpp b/src/win32/cairo-dwrite-font.cpp index 1fcf4691f..480512000 100644 --- a/src/win32/cairo-dwrite-font.cpp +++ b/src/win32/cairo-dwrite-font.cpp @@ -178,7 +178,7 @@ private: static RefPtr mFactoryInstance; }; - +cairo_atomic_once_t DWriteFactory::mOnceFactories = CAIRO_ATOMIC_ONCE_INIT; RefPtr DWriteFactory::mFactoryInstance; RefPtr DWriteFactory::mFactoryInstance1; RefPtr DWriteFactory::mFactoryInstance2; @@ -187,8 +187,9 @@ RefPtr DWriteFactory::mFactoryInstance4; RefPtr DWriteFactory::mFactoryInstance8; RefPtr WICImagingFactory::mFactoryInstance; + +cairo_atomic_once_t DWriteFactory::mOnceSystemCollection = CAIRO_ATOMIC_ONCE_INIT; RefPtr DWriteFactory::mSystemCollection; -RefPtr DWriteFactory::mDefaultRenderingParams; RefPtr D2DFactory::mFactoryInstance; @@ -197,8 +198,15 @@ _create_rendering_params(IDWriteRenderingParams *params, const cairo_font_options_t *options, cairo_antialias_t antialias) { - if (!params) - params = DWriteFactory::DefaultRenderingParams(); + RefPtr default_rendering_params; + HRESULT hr; + + if (!params) { + hr = DWriteFactory::Instance()->CreateRenderingParams(&default_rendering_params); + assert(SUCCEEDED(hr)); + params = default_rendering_params.get(); + } + FLOAT gamma = params->GetGamma(); FLOAT enhanced_contrast = params->GetEnhancedContrast(); FLOAT clear_type_level = params->GetClearTypeLevel(); @@ -243,7 +251,6 @@ _create_rendering_params(IDWriteRenderingParams *params, if (!modified) return params; - HRESULT hr; RefPtr params1; hr = params->QueryInterface(¶ms1); if (FAILED(hr)) { diff --git a/src/win32/cairo-dwrite-private.hpp b/src/win32/cairo-dwrite-private.hpp index ac0c3614e..c2c06d2c1 100644 --- a/src/win32/cairo-dwrite-private.hpp +++ b/src/win32/cairo-dwrite-private.hpp @@ -39,13 +39,6 @@ #include "dwrite-extra.hpp" #include "d2d1-extra.hpp" -// DirectWrite is not available on all platforms. -typedef HRESULT (WINAPI*DWriteCreateFactoryFunc)( - DWRITE_FACTORY_TYPE factoryType, - REFIID iid, - IUnknown **factory -); - /* #cairo_scaled_font_t implementation */ struct _cairo_dwrite_scaled_font { cairo_scaled_font_t base; @@ -63,96 +56,61 @@ class DWriteFactory public: static RefPtr Instance() { - if (!mFactoryInstance) { -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-function-type" -#endif - HMODULE dwrite = _cairo_win32_load_library_from_system32 (L"dwrite.dll"); - DWriteCreateFactoryFunc createDWriteFactory = (DWriteCreateFactoryFunc) - GetProcAddress(dwrite, "DWriteCreateFactory"); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - if (createDWriteFactory) { - HRESULT hr = createDWriteFactory( - DWRITE_FACTORY_TYPE_SHARED, - __uuidof(IDWriteFactory), - reinterpret_cast(&mFactoryInstance)); - assert(SUCCEEDED(hr)); - } - } - return mFactoryInstance; + InitializeFactories(); + return mFactoryInstance; } static RefPtr Instance1() { - if (!mFactoryInstance1) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance1); - } - } - return mFactoryInstance1; + InitializeFactories(); + return mFactoryInstance1; } static RefPtr Instance2() { - if (!mFactoryInstance2) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance2); - } - } - return mFactoryInstance2; + InitializeFactories(); + return mFactoryInstance2; } static RefPtr Instance3() { - if (!mFactoryInstance3) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance3); - } - } - return mFactoryInstance3; + InitializeFactories(); + return mFactoryInstance3; } static RefPtr Instance4() { - if (!mFactoryInstance4) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance4); - } - } - return mFactoryInstance4; + InitializeFactories(); + return mFactoryInstance4; } static RefPtr Instance8() { - if (!mFactoryInstance8) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance8); - } - } + InitializeFactories(); return mFactoryInstance8; } static RefPtr SystemCollection() { - if (!mSystemCollection) { - if (Instance()) { - HRESULT hr = Instance()->GetSystemFontCollection(&mSystemCollection); - assert(SUCCEEDED(hr)); - } - } - return mSystemCollection; + /* The system font collection obtained from the shared factory + * is a singleton object. This means that we can cache it + * globally and use from any thread. + */ + + if (_cairo_atomic_init_once_enter (&mOnceSystemCollection)) { + HRESULT hr = Instance()->GetSystemFontCollection(&mSystemCollection); + assert(SUCCEEDED(hr)); + + _cairo_atomic_init_once_leave (&mOnceSystemCollection); + } + return mSystemCollection; } static RefPtr FindSystemFontFamily(const WCHAR *aFamilyName) { UINT32 idx; BOOL found; - if (!SystemCollection()) { - return NULL; - } + SystemCollection()->FindFamilyName(aFamilyName, &idx, &found); if (!found) { return NULL; @@ -163,25 +121,62 @@ public: return family; } - static RefPtr DefaultRenderingParams() +private: + static void InitializeFactories() { - if (!mDefaultRenderingParams) { - if (Instance()) { - Instance()->CreateRenderingParams(&mDefaultRenderingParams); - } - } - return mDefaultRenderingParams; + /* The shared IDWriteFactory is a singleton object (every call to + * DWriteCreateFactory returns the same object) and thus is safe + * for concurrent access. + */ + + if (_cairo_atomic_init_once_enter (&mOnceFactories)) { + typedef HRESULT + (WINAPI *pDWriteCreateFactory_t) (DWRITE_FACTORY_TYPE factoryType, + REFIID iid, + IUnknown **factory); + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + HMODULE dwrite = _cairo_win32_load_library_from_system32 (L"dwrite.dll"); + pDWriteCreateFactory_t pDWriteCreateFactory = (pDWriteCreateFactory_t) + GetProcAddress (dwrite, "DWriteCreateFactory"); +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + /* DWrite is based on nano-COM, which is COM as binary interface + * and call convention, but doesn't need the runtime library or + * registered informations. There's no need to enter a COM + * apartment here. + */ + HRESULT hr = pDWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, + __uuidof (IDWriteFactory), + reinterpret_cast(&mFactoryInstance)); + assert(SUCCEEDED(hr)); + + mFactoryInstance->QueryInterface(&mFactoryInstance1); + mFactoryInstance->QueryInterface(&mFactoryInstance2); + mFactoryInstance->QueryInterface(&mFactoryInstance3); + mFactoryInstance->QueryInterface(&mFactoryInstance4); + mFactoryInstance->QueryInterface(&mFactoryInstance8); + + _cairo_atomic_init_once_leave (&mOnceFactories); + } } private: + static cairo_atomic_once_t mOnceFactories; static RefPtr mFactoryInstance; static RefPtr mFactoryInstance1; static RefPtr mFactoryInstance2; static RefPtr mFactoryInstance3; static RefPtr mFactoryInstance4; static RefPtr mFactoryInstance8; + + static cairo_atomic_once_t mOnceSystemCollection; static RefPtr mSystemCollection; - static RefPtr mDefaultRenderingParams; }; class AutoDWriteGlyphRun : public DWRITE_GLYPH_RUN From 61dc0938bf951661ba6b246dc3d4a50e560d4080 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Mon, 9 Mar 2026 12:32:50 +0100 Subject: [PATCH 08/12] DWriteFactory: Release resources on library unload We can release all cached DWrite objects --- src/win32/cairo-dwrite-font.cpp | 6 ++++++ src/win32/cairo-dwrite-private.hpp | 18 ++++++++++++++++++ src/win32/cairo-win32-private.h | 7 +++++++ src/win32/cairo-win32-system.c | 11 +++++++++++ 4 files changed, 42 insertions(+) diff --git a/src/win32/cairo-dwrite-font.cpp b/src/win32/cairo-dwrite-font.cpp index 480512000..3f2b455f0 100644 --- a/src/win32/cairo-dwrite-font.cpp +++ b/src/win32/cairo-dwrite-font.cpp @@ -2437,3 +2437,9 @@ _cairo_dwrite_scaled_font_create_win32_scaled_font (cairo_scaled_font_t *scaled_ *new_font = font; return CAIRO_INT_STATUS_SUCCESS; } + +void +cairo_win32_dwrite_finalize () +{ + DWriteFactory::Finalize(); +} diff --git a/src/win32/cairo-dwrite-private.hpp b/src/win32/cairo-dwrite-private.hpp index c2c06d2c1..5e1168cd1 100644 --- a/src/win32/cairo-dwrite-private.hpp +++ b/src/win32/cairo-dwrite-private.hpp @@ -121,6 +121,24 @@ public: return family; } + static void Finalize() + { + /* Loader-lock-safe */ + + if (_cairo_atomic_init_once_check (&mOnceSystemCollection)) { + cairo_win32_async_com_release (mSystemCollection.forget().drop()); + } + + if (_cairo_atomic_init_once_check (&mOnceFactories)) { + cairo_win32_async_com_release (mFactoryInstance.forget().drop()); + cairo_win32_async_com_release (mFactoryInstance1.forget().drop()); + cairo_win32_async_com_release (mFactoryInstance2.forget().drop()); + cairo_win32_async_com_release (mFactoryInstance3.forget().drop()); + cairo_win32_async_com_release (mFactoryInstance4.forget().drop()); + cairo_win32_async_com_release (mFactoryInstance8.forget().drop()); + } + } + private: static void InitializeFactories() { diff --git a/src/win32/cairo-win32-private.h b/src/win32/cairo-win32-private.h index 410a95aa8..25662ef48 100644 --- a/src/win32/cairo-win32-private.h +++ b/src/win32/cairo-win32-private.h @@ -45,6 +45,7 @@ #include "cairo-win32.h" #include +#include #define WIN32_FONT_LOGICAL_SCALE 32 @@ -243,6 +244,9 @@ typedef DWORD (__stdcall *stdcall_free_func_t) (void *); void cairo_win32_async_stdcall_free (stdcall_free_func_t func, void *data); +void +cairo_win32_async_com_release (IUnknown *iface_ptr); + typedef struct { HDC hdc; cairo_bool_t free_hdc; @@ -277,6 +281,9 @@ cairo_int_status_t _cairo_dwrite_scaled_font_create_win32_scaled_font (cairo_scaled_font_t *scaled_font, cairo_scaled_font_t **new_font); +void +cairo_win32_dwrite_finalize (void); + #endif /* CAIRO_HAS_DWRITE_FONT */ CAIRO_END_DECLS diff --git a/src/win32/cairo-win32-system.c b/src/win32/cairo-win32-system.c index ce7ece96f..9397b5a9f 100644 --- a/src/win32/cairo-win32-system.c +++ b/src/win32/cairo-win32-system.c @@ -115,6 +115,15 @@ cairo_win32_async_stdcall_free (stdcall_free_func_t func, void *data) QueueUserWorkItem (func, data, WT_EXECUTEDEFAULT); } +void +cairo_win32_async_com_release (IUnknown *iface_ptr) +{ + if (iface_ptr) { + QueueUserWorkItem ((void *) iface_ptr->lpVtbl->Release, + iface_ptr, WT_EXECUTEDEFAULT); + } +} + static void cairo_win32_initialize (void) { @@ -125,6 +134,8 @@ cairo_win32_initialize (void) static void cairo_win32_finalize (void) { + cairo_win32_dwrite_finalize (); + cairo_win32_thread_data_finalize (); CAIRO_MUTEX_FINALIZE (); } From efd0cad9ae142d19edb2faf967ad6cc84da62cb5 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Mon, 9 Mar 2026 12:44:20 +0100 Subject: [PATCH 09/12] D2DFactory: Make thread-safe --- src/win32/cairo-dwrite-font.cpp | 51 +++++++++++++++-------------- src/win32/cairo-win32-private.h | 8 +++++ src/win32/cairo-win32-thread-data.c | 10 ++++++ 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/win32/cairo-dwrite-font.cpp b/src/win32/cairo-dwrite-font.cpp index 3f2b455f0..24988cfc7 100644 --- a/src/win32/cairo-dwrite-font.cpp +++ b/src/win32/cairo-dwrite-font.cpp @@ -71,13 +71,6 @@ * Since: 1.18 **/ -typedef HRESULT (WINAPI*D2D1CreateFactoryFunc)( - D2D1_FACTORY_TYPE factoryType, - REFIID iid, - CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, - void **factory -); - #define CAIRO_INT_STATUS_SUCCESS (cairo_int_status_t)CAIRO_STATUS_SUCCESS // Forward declarations @@ -133,31 +126,41 @@ class D2DFactory public: static RefPtr Instance() { - if (!mFactoryInstance) { + /* According to MSDN, using independent, single-threaded D2D1 factories + * in each thread is the most scalable solution. + */ + cairo_win32_thread_data_t *thread_data = cairo_win32_thread_data_get (); + + if (!thread_data->d2d1_factory) { + typedef HRESULT + (WINAPI *pD2D1CreateFactory_t) (D2D1_FACTORY_TYPE factoryType, + REFIID iid, + CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, + void **factory); #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" #endif + /* TODO */ HMODULE d2d1 = _cairo_win32_load_library_from_system32 (L"d2d1.dll"); - D2D1CreateFactoryFunc createD2DFactory = (D2D1CreateFactoryFunc) - GetProcAddress(d2d1, "D2D1CreateFactory"); + pD2D1CreateFactory_t pD2D1CreateFactory = (pD2D1CreateFactory_t) + GetProcAddress (d2d1, "D2D1CreateFactory"); #ifdef __GNUC__ #pragma GCC diagnostic pop #endif - if (createD2DFactory) { - D2D1_FACTORY_OPTIONS options; - options.debugLevel = D2D1_DEBUG_LEVEL_NONE; - createD2DFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, - __uuidof(ID2D1Factory), - &options, - (void**)&mFactoryInstance); - } - } - return mFactoryInstance; - } + /* D2D1 is based on nano-COM (just like DWrite), so there's no need + * to ensure an apartment with CoInitializeEx or the implicit MTA. + */ + D2D1_FACTORY_OPTIONS options { D2D1_DEBUG_LEVEL_NONE }; + HRESULT hr = pD2D1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED, + __uuidof (ID2D1Factory), + &options, + (void**) &thread_data->d2d1_factory); + assert (SUCCEEDED (hr)); + } -private: - static RefPtr mFactoryInstance; + return thread_data->d2d1_factory; + } }; class WICImagingFactory @@ -191,8 +194,6 @@ RefPtr WICImagingFactory::mFactoryInstance; cairo_atomic_once_t DWriteFactory::mOnceSystemCollection = CAIRO_ATOMIC_ONCE_INIT; RefPtr DWriteFactory::mSystemCollection; -RefPtr D2DFactory::mFactoryInstance; - static RefPtr _create_rendering_params(IDWriteRenderingParams *params, const cairo_font_options_t *options, diff --git a/src/win32/cairo-win32-private.h b/src/win32/cairo-win32-private.h index 25662ef48..692da0de4 100644 --- a/src/win32/cairo-win32-private.h +++ b/src/win32/cairo-win32-private.h @@ -247,10 +247,18 @@ cairo_win32_async_stdcall_free (stdcall_free_func_t func, void *data); void cairo_win32_async_com_release (IUnknown *iface_ptr); +#if CAIRO_HAS_DWRITE_FONT +interface ID2D1Factory; +#endif + typedef struct { HDC hdc; cairo_bool_t free_hdc; +#if CAIRO_HAS_DWRITE_FONT + interface ID2D1Factory *d2d1_factory; +#endif + cairo_bool_t added_to_list; } cairo_win32_thread_data_t; diff --git a/src/win32/cairo-win32-thread-data.c b/src/win32/cairo-win32-thread-data.c index 169f6b9ce..914144dd4 100644 --- a/src/win32/cairo-win32-thread-data.c +++ b/src/win32/cairo-win32-thread-data.c @@ -107,6 +107,16 @@ thread_data_free (cairo_win32_thread_data_t *data) cairo_win32_async_stdcall_free (free_func, data->hdc); } +#if CAIRO_HAS_DWRITE_FONT + /* It's not clear if we can release DWrite objects from DllMain + * or in general while holding the loader lock. For one, this + * is not allowed for DXGI factories (refer to "DXGI responses + * from DLLMain" in MSDN's "DXGI Overview"). Use an asynchronous + * release to ensure safety. + */ + cairo_win32_async_com_release ((IUnknown*) data->d2d1_factory); +#endif + #ifdef USE_EXPLICIT_TLS thread_data_allocation_free (data); #endif From df558f592b3f473ffd3d595ec981124853e3c146 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Tue, 10 Mar 2026 16:36:18 +0100 Subject: [PATCH 10/12] WICFactory: Make thread-safe This fixes two issues: 1. A global IWICImagingFactory interface pointer was used directly from any apartment without marshaling. This goes against the rules of COM, and for all we know could lead to threading issues (perhaps it's not a problem in practice, but can't tell). 2. The COM apartment where the IWICImagingFactory is created could be finalized by the user. We take a reference on the STA but the thread could terminate. If the thread is on the MTA we don't take a reference at all. In such case the interface pointer would not be valid anymore (per the COM rules), but most importantly the WIC DLL is unloaded (this actually happens). To avoid that we create a IWICImagingFactory transiently each time we need it. We could cache the interface pointer using something like IInitializeSpy or the COM static store [1]. However performance is still great and we'll likely drop use of WIC soon (see MR !607). References: 1. The COM static store, part 1: Introduction https://devblogs.microsoft.com/oldnewthing/20210208-00/?p=104812 --- src/win32/cairo-dwrite-font.cpp | 41 +++++++---- src/win32/cairo-win32-private.h | 6 ++ src/win32/cairo-win32-system.c | 116 +++++++++++++++++++++++++++++++- 3 files changed, 149 insertions(+), 14 deletions(-) diff --git a/src/win32/cairo-dwrite-font.cpp b/src/win32/cairo-dwrite-font.cpp index 24988cfc7..f9db37bbf 100644 --- a/src/win32/cairo-dwrite-font.cpp +++ b/src/win32/cairo-dwrite-font.cpp @@ -168,17 +168,35 @@ class WICImagingFactory public: static RefPtr Instance() { - if (!mFactoryInstance) { - CoInitialize(NULL); - CoCreateInstance(CLSID_WICImagingFactory, - NULL, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&mFactoryInstance)); - } - return mFactoryInstance; + HRESULT hr; + + /* WIC is based on true COM, so we need to set this thread + * on a COM apartment. Usually one calls CoInitialize and + * call it a day, however this is used on threads we don't + * own. We can use whatever apartment the user has already + * initialized, but if there's no apartment we don't want + * to force the thread to a specific apartment type. Turns + * out implicit MTA is perfect for this; we have to take + * a reference on the MTA however, otherwise it can disappear + * at any time in the middle of our operations. + * + * Note: WICImagingFactory has threading model 'both', so + * the object will be accessed directly (no marshaling) + * regardless of the apartment type. + */ + cairo_win32_ensure_mta (); + + IWICImagingFactory *wic_factory; + hr = CoCreateInstance (CLSID_WICImagingFactory, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS (&wic_factory)); + if (FAILED (hr)) { + assert (0 && "CoCreateInstance (CLSID_WICImagingFactory) failed"); + } + + return wic_factory; } -private: - static RefPtr mFactoryInstance; }; cairo_atomic_once_t DWriteFactory::mOnceFactories = CAIRO_ATOMIC_ONCE_INIT; @@ -188,9 +206,6 @@ RefPtr DWriteFactory::mFactoryInstance2; RefPtr DWriteFactory::mFactoryInstance3; RefPtr DWriteFactory::mFactoryInstance4; RefPtr DWriteFactory::mFactoryInstance8; - -RefPtr WICImagingFactory::mFactoryInstance; - cairo_atomic_once_t DWriteFactory::mOnceSystemCollection = CAIRO_ATOMIC_ONCE_INIT; RefPtr DWriteFactory::mSystemCollection; diff --git a/src/win32/cairo-win32-private.h b/src/win32/cairo-win32-private.h index 692da0de4..7596e4e64 100644 --- a/src/win32/cairo-win32-private.h +++ b/src/win32/cairo-win32-private.h @@ -181,6 +181,9 @@ _cairo_win32_gdi_compositor_get (void); cairo_status_t _cairo_win32_print_api_error (const char *context, const char *api); +cairo_status_t +_cairo_win32_api_error_fatal (const char *format, ...); + cairo_bool_t _cairo_surface_is_win32 (const cairo_surface_t *surface); @@ -239,6 +242,9 @@ cairo_win32_get_system_text_quality (void); HMODULE _cairo_win32_load_library_from_system32 (const wchar_t *name); +void +cairo_win32_ensure_mta (void); + typedef DWORD (__stdcall *stdcall_free_func_t) (void *); void diff --git a/src/win32/cairo-win32-system.c b/src/win32/cairo-win32-system.c index 9397b5a9f..8f24150bd 100644 --- a/src/win32/cairo-win32-system.c +++ b/src/win32/cairo-win32-system.c @@ -50,6 +50,27 @@ #include +#include + +typedef HRESULT (__stdcall *pCoIncrementMTAUsage_t) (CO_MTA_USAGE_COOKIE*); +typedef HRESULT (__stdcall *pCoDecrementMTAUsage_t) (CO_MTA_USAGE_COOKIE); + +static struct { + cairo_atomic_once_t once; + + struct { + CO_MTA_USAGE_COOKIE cookie; + bool cookie_is_set; + pCoDecrementMTAUsage_t pCoDecrementMTAUsage; + } mta_usage; + HANDLE thread; +} mta = +{ + CAIRO_ATOMIC_ONCE_INIT, + { 0, false, NULL }, + NULL, +}; + /** * _cairo_win32_print_api_error: * @context: context string to display along with the error @@ -109,6 +130,99 @@ _cairo_win32_load_library_from_system32 (const wchar_t *name) return module_handle; } +static DWORD __stdcall +mta_thread_main (void *user_data) +{ + HRESULT hr; + + hr = CoInitializeEx (NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); + assert (SUCCEEDED (hr)); + + HANDLE event = (HANDLE) user_data; + if (!SignalObjectAndWait (event, GetCurrentProcess (), INFINITE, FALSE)) { + assert (0 && "SignalObjectAndWait failed"); + } + + return 0; +} + +/** + * cairo_win32_ensure_mta: + * + * Ensures that the MTA is initialized and keeps running in this + * process. Helps for COM usage on threads that we don't own, + * since we don't have to call CoInitializeEx. + **/ +void +cairo_win32_ensure_mta (void) +{ + if (_cairo_atomic_init_once_enter (&mta.once)) { + HMODULE ole32 = _cairo_win32_load_library_from_system32 (L"OLE32.DLL"); + + /* Windows 8+ */ + if (ole32) { + pCoIncrementMTAUsage_t pCoIncrementMTAUsage = + (pCoIncrementMTAUsage_t) GetProcAddress (ole32, "CoIncrementMTAUsage"); + pCoDecrementMTAUsage_t pCoDecrementMTAUsage = + (pCoDecrementMTAUsage_t) GetProcAddress (ole32, "CoDecrementMTAUsage"); + + if (pCoIncrementMTAUsage && pCoDecrementMTAUsage && + SUCCEEDED (pCoIncrementMTAUsage (&mta.mta_usage.cookie))) + { + mta.mta_usage.cookie_is_set = true; + mta.mta_usage.pCoDecrementMTAUsage = pCoDecrementMTAUsage; + } + } + + /* Downlevel support for Windows 7 */ + if (!mta.mta_usage.cookie_is_set) { + HANDLE event = CreateEvent (NULL, TRUE, FALSE, NULL); + if (!event) { + assert (0 && "CreateEvent failed"); + } + + /* Since the UCRT _beginthreadex takes a reference on the "calling + * HMODULE", which makes Cairo unloadable. Use CreateThread. + */ + mta.thread = CreateThread (NULL, 0, mta_thread_main, event, 0, NULL); + if (!mta.thread) { + assert (0 && "_beginthreadex failed"); + } + + DWORD ret = WaitForSingleObject (event, INFINITE); + if (ret != WAIT_OBJECT_0) { + assert (0 && "WaitForSingleObject failed"); + } + + CloseHandle (event); + } + + _cairo_atomic_init_once_leave (&mta.once); + } +} + +static void +cairo_win32_mta_finalize (void) +{ + /* Loader-lock-safe */ + + if (_cairo_atomic_init_once_check (&mta.once)) { + if (mta.mta_usage.cookie_is_set) { + void *free_func = mta.mta_usage.pCoDecrementMTAUsage; + cairo_win32_async_stdcall_free (free_func, mta.mta_usage.cookie); + } + else if (mta.thread) { + /* Yeah, TerminateThread is generally unsafe. however, this is synchronized + * with entering of kernel-mode (SignalObjectAndWait) and thus is completely + * safe. Note also that TerminateThread is asynchronous, so it can be used + * from DllMain. + */ + TerminateThread (mta.thread, 0); + CloseHandle (mta.thread); + } + } +} + void cairo_win32_async_stdcall_free (stdcall_free_func_t func, void *data) { @@ -135,8 +249,8 @@ static void cairo_win32_finalize (void) { cairo_win32_dwrite_finalize (); - cairo_win32_thread_data_finalize (); + cairo_win32_mta_finalize (); CAIRO_MUTEX_FINALIZE (); } From ebbc6b1479e8c32ab37f6a9926311ecf09b68c35 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Tue, 10 Mar 2026 16:37:58 +0100 Subject: [PATCH 11/12] DWriteFactory: return raw pointers Lifetimes are tied to the DLL so callers cannot extend it anyway This also helps with performance a tiny bit by removing unneeded atomic increments / decrements which can churn CPU caches in multi- threaded scenarios. --- src/win32/cairo-dwrite-font.cpp | 18 +++++----- src/win32/cairo-dwrite-private.hpp | 58 +++++++++++++++++------------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/win32/cairo-dwrite-font.cpp b/src/win32/cairo-dwrite-font.cpp index f9db37bbf..ad1d45868 100644 --- a/src/win32/cairo-dwrite-font.cpp +++ b/src/win32/cairo-dwrite-font.cpp @@ -200,14 +200,14 @@ public: }; cairo_atomic_once_t DWriteFactory::mOnceFactories = CAIRO_ATOMIC_ONCE_INIT; -RefPtr DWriteFactory::mFactoryInstance; -RefPtr DWriteFactory::mFactoryInstance1; -RefPtr DWriteFactory::mFactoryInstance2; -RefPtr DWriteFactory::mFactoryInstance3; -RefPtr DWriteFactory::mFactoryInstance4; -RefPtr DWriteFactory::mFactoryInstance8; +IDWriteFactory *DWriteFactory::mFactoryInstance; +IDWriteFactory1 *DWriteFactory::mFactoryInstance1; +IDWriteFactory2 *DWriteFactory::mFactoryInstance2; +IDWriteFactory3 *DWriteFactory::mFactoryInstance3; +IDWriteFactory4 *DWriteFactory::mFactoryInstance4; +IDWriteFactory8 *DWriteFactory::mFactoryInstance8; cairo_atomic_once_t DWriteFactory::mOnceSystemCollection = CAIRO_ATOMIC_ONCE_INIT; -RefPtr DWriteFactory::mSystemCollection; +IDWriteFontCollection *DWriteFactory::mSystemCollection; static RefPtr _create_rendering_params(IDWriteRenderingParams *params, @@ -1148,7 +1148,7 @@ _cairo_dwrite_scaled_font_init_glyph_color_surface(cairo_dwrite_scaled_font_t *s if (scaled_font->base.options.palette_index < palette_count) palette_index = scaled_font->base.options.palette_index; - if (DWriteFactory::Instance8().get()) { + if (DWriteFactory::Instance8()) { hr = DWriteFactory::Instance8()->TranslateColorGlyphRun( origin, &run, @@ -1161,7 +1161,7 @@ _cairo_dwrite_scaled_font_init_glyph_color_surface(cairo_dwrite_scaled_font_t *s palette_index, &run_enumerator); } - else if (DWriteFactory::Instance4().get()) { + else if (DWriteFactory::Instance4()) { hr = DWriteFactory::Instance4()->TranslateColorGlyphRun( origin, &run, diff --git a/src/win32/cairo-dwrite-private.hpp b/src/win32/cairo-dwrite-private.hpp index 5e1168cd1..c3b6369b5 100644 --- a/src/win32/cairo-dwrite-private.hpp +++ b/src/win32/cairo-dwrite-private.hpp @@ -54,43 +54,50 @@ typedef struct _cairo_dwrite_scaled_font cairo_dwrite_scaled_font_t; class DWriteFactory { public: - static RefPtr Instance() + static IDWriteFactory * + Instance() { InitializeFactories(); return mFactoryInstance; } - static RefPtr Instance1() + static IDWriteFactory1 * + Instance1() { InitializeFactories(); return mFactoryInstance1; } - static RefPtr Instance2() + static IDWriteFactory2 * + Instance2() { InitializeFactories(); return mFactoryInstance2; } - static RefPtr Instance3() + static IDWriteFactory3 * + Instance3() { InitializeFactories(); return mFactoryInstance3; } - static RefPtr Instance4() + static IDWriteFactory4 * + Instance4() { InitializeFactories(); return mFactoryInstance4; } - static RefPtr Instance8() + static IDWriteFactory8 * + Instance8() { InitializeFactories(); return mFactoryInstance8; } - static RefPtr SystemCollection() + static IDWriteFontCollection * + SystemCollection() { /* The system font collection obtained from the shared factory * is a singleton object. This means that we can cache it @@ -106,7 +113,8 @@ public: return mSystemCollection; } - static RefPtr FindSystemFontFamily(const WCHAR *aFamilyName) + static RefPtr + FindSystemFontFamily(const WCHAR *aFamilyName) { UINT32 idx; BOOL found; @@ -121,26 +129,28 @@ public: return family; } - static void Finalize() + static void + Finalize() { /* Loader-lock-safe */ if (_cairo_atomic_init_once_check (&mOnceSystemCollection)) { - cairo_win32_async_com_release (mSystemCollection.forget().drop()); + cairo_win32_async_com_release (mSystemCollection); } if (_cairo_atomic_init_once_check (&mOnceFactories)) { - cairo_win32_async_com_release (mFactoryInstance.forget().drop()); - cairo_win32_async_com_release (mFactoryInstance1.forget().drop()); - cairo_win32_async_com_release (mFactoryInstance2.forget().drop()); - cairo_win32_async_com_release (mFactoryInstance3.forget().drop()); - cairo_win32_async_com_release (mFactoryInstance4.forget().drop()); - cairo_win32_async_com_release (mFactoryInstance8.forget().drop()); + cairo_win32_async_com_release (mFactoryInstance); + cairo_win32_async_com_release (mFactoryInstance1); + cairo_win32_async_com_release (mFactoryInstance2); + cairo_win32_async_com_release (mFactoryInstance3); + cairo_win32_async_com_release (mFactoryInstance4); + cairo_win32_async_com_release (mFactoryInstance8); } } private: - static void InitializeFactories() + static void + InitializeFactories() { /* The shared IDWriteFactory is a singleton object (every call to * DWriteCreateFactory returns the same object) and thus is safe @@ -186,15 +196,15 @@ private: private: static cairo_atomic_once_t mOnceFactories; - static RefPtr mFactoryInstance; - static RefPtr mFactoryInstance1; - static RefPtr mFactoryInstance2; - static RefPtr mFactoryInstance3; - static RefPtr mFactoryInstance4; - static RefPtr mFactoryInstance8; + static IDWriteFactory *mFactoryInstance; + static IDWriteFactory1 *mFactoryInstance1; + static IDWriteFactory2 *mFactoryInstance2; + static IDWriteFactory3 *mFactoryInstance3; + static IDWriteFactory4 *mFactoryInstance4; + static IDWriteFactory8 *mFactoryInstance8; static cairo_atomic_once_t mOnceSystemCollection; - static RefPtr mSystemCollection; + static IDWriteFontCollection *mSystemCollection; }; class AutoDWriteGlyphRun : public DWRITE_GLYPH_RUN From d7b57cb8d1e6af5e78b2361691bb9d14575d1bd3 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Tue, 10 Mar 2026 16:44:50 +0100 Subject: [PATCH 12/12] D2DFactory: return raw pointers See previous commit for details --- src/win32/cairo-dwrite-font.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/win32/cairo-dwrite-font.cpp b/src/win32/cairo-dwrite-font.cpp index ad1d45868..0c54153cf 100644 --- a/src/win32/cairo-dwrite-font.cpp +++ b/src/win32/cairo-dwrite-font.cpp @@ -124,7 +124,8 @@ _cairo_dwrite_error (HRESULT hr, const char *context) class D2DFactory { public: - static RefPtr Instance() + static ID2D1Factory * + Instance() { /* According to MSDN, using independent, single-threaded D2D1 factories * in each thread is the most scalable solution.