Merge branch 'win32-thread-data' into 'master'

DWrite font backend thread-safety

Closes #886, #952, and #953

See merge request cairo/cairo!650
This commit is contained in:
Luca Bacci 2026-05-07 19:20:37 +00:00
commit 0a547172dd
8 changed files with 673 additions and 276 deletions

View file

@ -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

View file

@ -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',

View file

@ -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<ID2D1Factory> 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<IDWriteFactory4> Instance4()
{
if (!mFactoryInstance4) {
if (Instance()) {
Instance()->QueryInterface(&mFactoryInstance4);
}
}
return mFactoryInstance4;
}
static RefPtr<ID2D1DCRenderTarget> 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<ID2D1Factory> mFactoryInstance;
static RefPtr<IDWriteFactory4> mFactoryInstance4;
static RefPtr<ID2D1DCRenderTarget> mRenderTarget;
};
class WICImagingFactory
@ -200,41 +169,61 @@ class WICImagingFactory
public:
static RefPtr<IWICImagingFactory> 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<IWICImagingFactory> mFactoryInstance;
};
RefPtr<IDWriteFactory> DWriteFactory::mFactoryInstance;
RefPtr<IDWriteFactory1> DWriteFactory::mFactoryInstance1;
RefPtr<IDWriteFactory2> DWriteFactory::mFactoryInstance2;
RefPtr<IDWriteFactory3> DWriteFactory::mFactoryInstance3;
RefPtr<IDWriteFactory4> DWriteFactory::mFactoryInstance4;
RefPtr<IDWriteFactory8> DWriteFactory::mFactoryInstance8;
RefPtr<IWICImagingFactory> WICImagingFactory::mFactoryInstance;
RefPtr<IDWriteFontCollection> DWriteFactory::mSystemCollection;
RefPtr<IDWriteRenderingParams> DWriteFactory::mDefaultRenderingParams;
RefPtr<ID2D1Factory> D2DFactory::mFactoryInstance;
RefPtr<ID2D1DCRenderTarget> 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<IDWriteRenderingParams>
_create_rendering_params(IDWriteRenderingParams *params,
const cairo_font_options_t *options,
cairo_antialias_t antialias)
{
if (!params)
params = DWriteFactory::DefaultRenderingParams();
RefPtr<IDWriteRenderingParams> 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<IDWriteRenderingParams1> params1;
hr = params->QueryInterface(&params1);
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<ID2D1DCRenderTarget> 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<ID2D1SolidColorBrush> 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(&copyArea, &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();
}

View file

@ -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<IDWriteFactory> 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<IUnknown**>(&mFactoryInstance));
assert(SUCCEEDED(hr));
}
}
return mFactoryInstance;
InitializeFactories();
return mFactoryInstance;
}
static RefPtr<IDWriteFactory1> Instance1()
static IDWriteFactory1 *
Instance1()
{
if (!mFactoryInstance1) {
if (Instance()) {
Instance()->QueryInterface(&mFactoryInstance1);
}
}
return mFactoryInstance1;
InitializeFactories();
return mFactoryInstance1;
}
static RefPtr<IDWriteFactory2> Instance2()
static IDWriteFactory2 *
Instance2()
{
if (!mFactoryInstance2) {
if (Instance()) {
Instance()->QueryInterface(&mFactoryInstance2);
}
}
return mFactoryInstance2;
InitializeFactories();
return mFactoryInstance2;
}
static RefPtr<IDWriteFactory3> Instance3()
static IDWriteFactory3 *
Instance3()
{
if (!mFactoryInstance3) {
if (Instance()) {
Instance()->QueryInterface(&mFactoryInstance3);
}
}
return mFactoryInstance3;
InitializeFactories();
return mFactoryInstance3;
}
static RefPtr<IDWriteFactory4> Instance4()
static IDWriteFactory4 *
Instance4()
{
if (!mFactoryInstance4) {
if (Instance()) {
Instance()->QueryInterface(&mFactoryInstance4);
}
}
return mFactoryInstance4;
InitializeFactories();
return mFactoryInstance4;
}
static RefPtr<IDWriteFactory8> Instance8()
static IDWriteFactory8 *
Instance8()
{
if (!mFactoryInstance8) {
if (Instance()) {
Instance()->QueryInterface(&mFactoryInstance8);
}
}
InitializeFactories();
return mFactoryInstance8;
}
static RefPtr<IDWriteFontCollection> 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<IDWriteFontFamily> FindSystemFontFamily(const WCHAR *aFamilyName)
static RefPtr<IDWriteFontFamily>
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<IDWriteRenderingParams> 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<IDWriteFactory> mFactoryInstance;
static RefPtr<IDWriteFactory1> mFactoryInstance1;
static RefPtr<IDWriteFactory2> mFactoryInstance2;
static RefPtr<IDWriteFactory3> mFactoryInstance3;
static RefPtr<IDWriteFactory4> mFactoryInstance4;
static RefPtr<IDWriteFactory8> mFactoryInstance8;
static RefPtr<IDWriteFontCollection> mSystemCollection;
static RefPtr<IDWriteRenderingParams> 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<IUnknown**>(&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

View file

@ -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 ();

View file

@ -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 <windows.h>
#include <unknwn.h>
#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

View file

@ -46,8 +46,31 @@
#include "cairoint.h"
#include "cairo-win32-private.h"
#include <windows.h>
#include <stdbool.h>
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 */

View file

@ -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 <luca.bacci@outlook.com>
*/
#include "cairoint.h"
#include "cairo-array-private.h"
#include "cairo-win32-private.h"
#include <windows.h>
#include <stdlib.h>
#include <malloc.h>
#include <assert.h>
#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);
}