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 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-dwrite-font.cpp b/src/win32/cairo-dwrite-font.cpp index 37e578a60..ae3b01212 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 @@ -131,68 +124,44 @@ _cairo_dwrite_error (HRESULT hr, const char *context) class D2DFactory { public: - static RefPtr Instance() + static ID2D1Factory * + 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)); + } + + return thread_data->d2d1_factory; } - - static RefPtr Instance4() - { - if (!mFactoryInstance4) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance4); - } - } - 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 @@ -200,41 +169,61 @@ 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; }; - -RefPtr DWriteFactory::mFactoryInstance; -RefPtr DWriteFactory::mFactoryInstance1; -RefPtr DWriteFactory::mFactoryInstance2; -RefPtr DWriteFactory::mFactoryInstance3; -RefPtr DWriteFactory::mFactoryInstance4; -RefPtr DWriteFactory::mFactoryInstance8; - -RefPtr WICImagingFactory::mFactoryInstance; -RefPtr DWriteFactory::mSystemCollection; -RefPtr DWriteFactory::mDefaultRenderingParams; - -RefPtr D2DFactory::mFactoryInstance; -RefPtr D2DFactory::mRenderTarget; +cairo_atomic_once_t DWriteFactory::mOnceFactories = CAIRO_ATOMIC_ONCE_INIT; +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; +IDWriteFontCollection *DWriteFactory::mSystemCollection; static RefPtr _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(); @@ -279,7 +268,6 @@ _create_rendering_params(IDWriteRenderingParams *params, if (!modified) return params; - HRESULT hr; RefPtr params1; hr = params->QueryInterface(¶ms1); if (FAILED(hr)) { @@ -1161,7 +1149,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, @@ -1174,7 +1162,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, @@ -2202,52 +2190,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, @@ -2321,25 +2263,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; } @@ -2535,3 +2459,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 ac0c3614e..c3b6369b5 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; @@ -61,98 +54,71 @@ typedef struct _cairo_dwrite_scaled_font cairo_dwrite_scaled_font_t; class DWriteFactory { public: - static RefPtr Instance() + static IDWriteFactory * + 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() + static IDWriteFactory1 * + Instance1() { - if (!mFactoryInstance1) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance1); - } - } - return mFactoryInstance1; + InitializeFactories(); + return mFactoryInstance1; } - static RefPtr Instance2() + static IDWriteFactory2 * + Instance2() { - if (!mFactoryInstance2) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance2); - } - } - return mFactoryInstance2; + InitializeFactories(); + return mFactoryInstance2; } - static RefPtr Instance3() + static IDWriteFactory3 * + Instance3() { - if (!mFactoryInstance3) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance3); - } - } - return mFactoryInstance3; + InitializeFactories(); + return mFactoryInstance3; } - static RefPtr Instance4() + static IDWriteFactory4 * + Instance4() { - if (!mFactoryInstance4) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance4); - } - } - return mFactoryInstance4; + InitializeFactories(); + return mFactoryInstance4; } - static RefPtr Instance8() + static IDWriteFactory8 * + Instance8() { - if (!mFactoryInstance8) { - if (Instance()) { - Instance()->QueryInterface(&mFactoryInstance8); - } - } + InitializeFactories(); return mFactoryInstance8; } - static RefPtr SystemCollection() + static IDWriteFontCollection * + 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) + 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 +129,82 @@ public: return family; } - static RefPtr DefaultRenderingParams() + static void + Finalize() { - if (!mDefaultRenderingParams) { - if (Instance()) { - Instance()->CreateRenderingParams(&mDefaultRenderingParams); - } - } - return mDefaultRenderingParams; + /* Loader-lock-safe */ + + if (_cairo_atomic_init_once_check (&mOnceSystemCollection)) { + cairo_win32_async_com_release (mSystemCollection); + } + + if (_cairo_atomic_init_once_check (&mOnceFactories)) { + 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 RefPtr mFactoryInstance; - static RefPtr mFactoryInstance1; - static RefPtr mFactoryInstance2; - static RefPtr mFactoryInstance3; - static RefPtr mFactoryInstance4; - static RefPtr mFactoryInstance8; - static RefPtr mSystemCollection; - static RefPtr mDefaultRenderingParams; + static void + InitializeFactories() + { + /* 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 IDWriteFactory *mFactoryInstance; + static IDWriteFactory1 *mFactoryInstance1; + static IDWriteFactory2 *mFactoryInstance2; + static IDWriteFactory3 *mFactoryInstance3; + static IDWriteFactory4 *mFactoryInstance4; + static IDWriteFactory8 *mFactoryInstance8; + + static cairo_atomic_once_t mOnceSystemCollection; + static IDWriteFontCollection *mSystemCollection; }; class AutoDWriteGlyphRun : public DWRITE_GLYPH_RUN diff --git a/src/win32/cairo-win32-font.c b/src/win32/cairo-win32-font.c index 21c19a141..67c09d14e 100644 --- a/src/win32/cairo-win32-font.c +++ b/src/win32/cairo-win32-font.c @@ -141,21 +141,15 @@ _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) { - 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 @@ -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 (); diff --git a/src/win32/cairo-win32-private.h b/src/win32/cairo-win32-private.h index a9eda8fdb..7596e4e64 100644 --- a/src/win32/cairo-win32-private.h +++ b/src/win32/cairo-win32-private.h @@ -36,14 +36,17 @@ #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 +#include + #define WIN32_FONT_LOGICAL_SCALE 32 CAIRO_BEGIN_DECLS @@ -178,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); @@ -236,6 +242,44 @@ 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 +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; + +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 @@ -251,6 +295,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 c5ef24e8d..8f24150bd 100644 --- a/src/win32/cairo-win32-system.c +++ b/src/win32/cairo-win32-system.c @@ -46,8 +46,31 @@ #include "cairoint.h" +#include "cairo-win32-private.h" + #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 @@ -107,20 +130,146 @@ _cairo_win32_load_library_from_system32 (const wchar_t *name) return module_handle; } -#if CAIRO_MUTEX_IMPL_WIN32 +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) +{ + 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) +{ + CAIRO_MUTEX_INITIALIZE (); + cairo_win32_thread_data_initialize (); +} + +static void +cairo_win32_finalize (void) +{ + cairo_win32_dwrite_finalize (); + cairo_win32_thread_data_finalize (); + cairo_win32_mta_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 +317,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..914144dd4 --- /dev/null +++ b/src/win32/cairo-win32-thread-data.c @@ -0,0 +1,223 @@ +/* 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 */ + + 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); + } + +#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 +} + +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); +}