Многопоточные приложения 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, но необходимо сделать это, чтобы избежать конфликтов доступа.
На схеме ниже показан конфликт доступа к ресурсам Direct3D из-за косвенного доступа потока T0 к ресурсу через вызов Direct2D и T2, обращаюющийся к тому же ресурсу напрямую через вызов Direct3D или DXGI.
Примечание
В этом случае защита потока, которую предоставляет Direct2D (синяя блокировка на этом изображении), не помогает.
Чтобы избежать конфликта доступа к ресурсам, мы рекомендуем явным образом получить блокировку, которую Direct2D использует для внутренней синхронизации доступа, и применить эту блокировку, когда потоку необходимо выполнить вызовы Direct3D или DXGI, которые могут вызвать конфликт доступа, как показано ниже. В частности, следует соблюдать особые требования к коду, который использует исключения или систему раннего выхода на основе кодов возврата HRESULT. По этой причине мы рекомендуем использовать шаблон RAII (инициализация приобретения ресурсов) для вызова методов ВВОД и Выход .
Примечание
Важно связать вызовы методов ENTER и Leave , в противном случае приложение может взаимоблокироваться.
В приведенном здесь коде показан пример блокировки, а затем разблокировки вызовов 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 ".
При использовании методов ENTER и Leave вызовы защищаются автоматическим Direct2D и явной блокировкой, поэтому приложение не попадает в конфликт доступа.
Существуют и другие подходы к решению этой проблемы. Тем не менее, мы рекомендуем явно защитить вызовы Direct3D или DXGI с помощью блокировки Direct2D , так как обычно это обеспечивает лучшую производительность, так как она защищает параллелизм на гораздо более тонком уровне и с меньшими издержками при крышке Direct2D.
Обеспечение атомарности операций с отслеживанием состояния
Хотя функции безопасности потоков DirectX могут помочь гарантировать, что два отдельных вызова API не выполняются одновременно, необходимо также убедиться, что потоки, выполняющие вызовы API с отслеживанием состояния, не мешают друг другу. Ниже приведен пример.
- Существует две строки текста, которые требуется отобразить как на экране (по потоку 0), так и за пределами экрана (по потоку 1): строка No 1 — "A больше", а строка #2 — "чем B", оба из которых будут нарисованы с помощью сплошной черной кисти.
- Поток 1 рисует первую строку текста.
- Поток 0 реагирует на ввод пользователем, обновляет текстовые строки на "Б меньше" и "чем A" соответственно, а цвет кисти изменен на сплошной красный для собственного рисунка;
- Поток 1 продолжает рисование второй строки текста, которая теперь является "чем А", с красной кистью цвета;
- Наконец, мы получаем две строки текста на целевом объекте рисования за пределами экрана: "A больше" в черном и "чем А" красным цветом.
В верхней строке поток 0 рисует с текущими текстовыми строками и текущей черной кистью. Поток 1 завершается только за пределами экрана в верхней половине.
В средней строке поток 0 реагирует на взаимодействие с пользователем, обновляет текстовые строки и кисть, а затем обновляет экран. На этом этапе поток 1 блокируется. В нижней строке окончательная отрисовка за пределами экрана после того, как поток 1 возобновляет рисование нижней половины с измененной кистью и измененной текстовой строкой.
Чтобы устранить эту проблему, рекомендуется иметь отдельный контекст для каждого потока, чтобы:
- Следует создать копию контекста устройства, чтобы изменяемые ресурсы (т. е. ресурсы, которые могут изменяться во время отображения или печати, например содержимое текста или сплошная кисть цвета в примере), не изменяются при отрисовке. В этом примере необходимо сохранить копию этих двух строк текста и цветовой кисти перед рисованием. Таким образом вы гарантируете, что каждый поток имеет полное и согласованное содержимое для рисования и представления.
- Следует совместно использовать ресурсы с большим весом (например, растровые рисунки и сложные диаграммы эффектов), которые инициализируются один раз, а затем никогда не изменялись в потоках, чтобы повысить производительность.
- Вы можете совместно использовать легковесовые ресурсы (например, кисти сплошного цвета и текстовые форматы), которые инициализируются один раз, а затем никогда не изменялись в потоках или нет.
Итоги
При разработке многопоточных приложений Direct2D необходимо создать многопототочную фабрику Direct2D, а затем наследовать все ресурсы Direct2D из этой фабрики. Если поток выполняет вызовы Direct3D или DXGI, необходимо также явно приобрести блокировку Direct2D, чтобы защитить эти вызовы Direct3D или DXGI. Кроме того, необходимо обеспечить целостность контекста, создав копию изменяемых ресурсов для каждого потока.