Многопоточные приложения Direct2D

При разработке приложений Direct2D может потребоваться доступ к ресурсам Direct2D из нескольких потоков. В других случаях может потребоваться использовать многопоточность, чтобы повысить производительность или повысить скорость отклика (например, использовать один поток для отображения экрана и отдельный поток для автономной отрисовки).

В этом разделе описываются рекомендации по разработке многопоточных приложений Direct2D без рендеринга Direct3D . Дефекты программного обеспечения, вызванные проблемами параллелизма, могут быть трудно найти, поэтому полезно спланировать политику многопоточности и следовать рекомендациям, описанным здесь.

Примечание

Если вы обращаетесь к двум ресурсам Direct2D, созданным на двух разных однопоточных фабриках Direct2D, это не приведет к конфликтам доступа, если базовые устройства Direct3D и контексты устройств также различаются. Когда в этой статье речь идет о "доступе к ресурсам Direct2D", это действительно означает "доступ к ресурсам Direct2D, созданным с того же устройства Direct2D", если не указано иное.

Разработка приложений Thread-Safe, которые вызывают только API Direct2D

Можно создать многопоточный экземпляр фабрики Direct2D . Вы можете использовать и совместно использовать многопототочную фабрику и все ее ресурсы из нескольких потоков, но доступ к этим ресурсам (через вызовы Direct2D) сериализуется Direct2D, поэтому конфликты доступа не возникают. Если приложение вызывает только API Direct2D, такая защита автоматически выполняется Direct2D на детальном уровне с минимальными издержками. Код для создания многопоточной фабрики здесь.

ID2D1Factory* m_D2DFactory;

// Create a Direct2D factory.
HRESULT hr = D2D1CreateFactory(
    D2D1_FACTORY_TYPE_MULTI_THREADED,
    &m_D2DFactory
);

На изображении здесь показано, как Direct2D сериализует два потока, которые делают вызовы только с помощью API Direct2D.

схема двух сериализованных потоков.

Разработка Thread-Safe приложений Direct2D с минимальным количеством вызовов Direct3D или DXGI

Чаще всего приложение Direct2D также выполняет некоторые вызовы Direct3D или DXGI. Например, поток отображения будет рисовать в Direct2D, а затем будет представлен с помощью цепочки буферов DXGI.

В этом случае обеспечить потокобезопасность сложнее: некоторые вызовы Direct2D косвенно обращаются к базовым ресурсам Direct3D , к которым может одновременно обращаться другой поток, вызывающий Direct3D или DXGI. Так как эти вызовы Direct3D или DXGI не находятся под контролем Direct2D, необходимо создать многопототочную фабрику Direct2D, но во избежание конфликтов доступа необходимо выполнить инструкции mor.

На схеме здесь показан конфликт доступа к ресурсам Direct3D из-за того, что поток T0 обращается к ресурсу косвенно через вызов Direct2D , а T2 обращается к тому же ресурсу напрямую через вызов Direct3D или DXGI.

Примечание

Защита потоков, которую обеспечивает Direct2D (синяя блокировка на этом изображении), не помогает в этом случае.

 

Схема защиты потоков.

Чтобы избежать конфликта доступа к ресурсам, рекомендуется явно получить блокировку, которую Direct2D использует для внутренней синхронизации доступа, и применить эту блокировку, когда потоку необходимо выполнять вызовы Direct3D или DXGI, которые могут вызвать конфликт доступа, как показано ниже. В частности, следует соблюдать особую осторожность с кодом, использующим исключения, или с системой раннего выхода на основе кодов возврата HRESULT. По этой причине рекомендуется использовать шаблон RAII (получение ресурсов — инициализация) для вызова методов Ввод и Выход .

Примечание

Важно связать вызовы методов Ввод и Выход , в противном случае приложение может заключиться в взаимоблокировку.

 

В приведенном здесь коде показан пример того, когда следует блокировать, а затем разблокировать вызовы Direct3D или DXGI.

void MyApp::DrawFromThread2()
{
    // We are accessing Direct3D resources directly without Direct2D's knowledge, so we
    // must manually acquire and apply the Direct2D factory lock.
    ID2D1Multithread* m_D2DMultithread;
    m_D2DFactory->QueryInterface(IID_PPV_ARGS(&m_D2DMultithread));
    m_D2DMultithread->Enter();
    
    // Now it is safe to make Direct3D/DXGI calls, such as IDXGISwapChain::Present
    MakeDirect3DCalls();

    // It is absolutely critical that the factory lock be released upon
    // exiting this function, or else any consequent Direct2D calls will be blocked.
    m_D2DMultithread->Leave();
}

Примечание

Некоторые вызовы Direct3D или DXGI (в частности , IDXGISwapChain::P resent) могут получать блокировки и /или обратные вызовы в код вызывающей функции или метода. Следует помнить об этом и убедиться, что такое поведение не приводит к взаимоблокировкам. Дополнительные сведения см. в разделе Обзор DXGI .

 

Схема блокировки потоков direct2d и direct3d.

При использовании методов Ввод и Выход вызовы защищаются автоматическим direct2D и явной блокировкой, чтобы приложение не столкнулось с конфликтом доступа.

Существуют и другие подходы к обходу этой проблемы. Однако мы рекомендуем явно защищать вызовы Direct3D или DXGI с помощью блокировки Direct2D , так как обычно это обеспечивает более высокую производительность, так как защищает параллелизм на гораздо более тонком уровне и с меньшими издержками при использовании Direct2D.

Обеспечение атомарности операций с отслеживанием состояния

Хотя функции потокобезопасности DirectX помогают гарантировать, что два отдельных вызова API не выполняются одновременно, необходимо также убедиться, что потоки, выполняющие вызовы API с отслеживанием состояния, не будут мешать друг другу. Ниже приведен пример.

  1. Есть две строки текста, которые необходимо отрисовать как на экране (поток 0), так и за экраном (поток 1): строка 1 — "A больше" и строка 2 — "чем B", обе строки будут нарисованы с помощью черной кисти.
  2. Поток 1 рисует первую строку текста.
  3. Поток 0 реагирует на введенные пользователем данные, обновляет обе текстовые строки на "B меньше" и "чем A" соответственно, а цвет кисти изменился на сплошной красный для собственного рисунка;
  4. Поток 1 продолжает рисование второй строки текста, которая теперь является "than A", с помощью кисти красного цвета;
  5. Наконец, мы получаем две строки текста на целевом объекте рисования за кадром: "A больше" черным цветом и "чем A" красным.

схема потоков на экране и за его выключением.

В верхней строке Поток 0 рисует с текущими текстовыми строками и текущей черной кистью. Поток 1 завершает только рисование вне экрана в верхней половине.

В средней строке Поток 0 реагирует на взаимодействие с пользователем, обновляет текстовые строки и кисть, а затем обновляет экран. На этом этапе поток 1 блокируется. В нижней строке после завершения отрисовки вне экрана после потока 1 возобновляется рисование нижней половины с измененной кистью и измененной текстовой строкой.

Чтобы устранить эту проблему, рекомендуется использовать отдельный контекст для каждого потока, чтобы:

  • Необходимо создать копию контекста устройства, чтобы изменяемые ресурсы (т. е. ресурсы, которые могут меняться во время отображения или печати, например текстовое содержимое или кисть сплошного цвета в примере) не изменялись при отрисовке. В этом примере перед рисованием следует сохранить копию этих двух строк текста и цветной кисти. Таким образом, вы гарантируете, что каждый поток имеет полное и согласованное содержимое для рисования и представления.
  • Вы должны совместно использовать ресурс с большим весом (например, растровые изображения и сложные графы эффектов), которые инициализируются один раз, а затем никогда не изменялись в потоках для повышения производительности.
  • Вы можете совместно использовать легкие ресурсы (например, кисти сплошного цвета и текстовые форматы), которые инициализируются один раз, а затем никогда не изменялись в разных потоках.

Сводка

При разработке многопоточных приложений Direct2D необходимо создать многопототочную фабрику Direct2D, а затем получить все ресурсы Direct2D из этой фабрики. Если поток будет выполнять вызовы Direct3D или DXGI, необходимо также явно получить, а затем применить блокировку Direct2D, чтобы защитить эти вызовы Direct3D или DXGI. Кроме того, необходимо обеспечить целостность контекста, имея копию изменяемых ресурсов для каждого потока.