From efd0cad9ae142d19edb2faf967ad6cc84da62cb5 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Mon, 9 Mar 2026 12:44:20 +0100 Subject: [PATCH] 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