Wielowątkowane aplikacje Direct2D
W przypadku tworzenia aplikacji Direct2D może być konieczne uzyskanie dostępu do zasobów Direct2D z więcej niż jednego wątku. W innych przypadkach warto użyć wielowątkowości, aby uzyskać lepszą wydajność lub lepszą szybkość reakcji (na przykład użycie jednego wątku do wyświetlania ekranu i oddzielnego wątku do renderowania w trybie offline).
W tym temacie opisano najlepsze rozwiązania dotyczące tworzenia wielowątkowych aplikacji Direct2D bez renderowania Direct3D. Wady oprogramowania spowodowane problemami współbieżności mogą być trudne do śledzenia i warto zaplanować zasady wielowątkowości i postępować zgodnie z najlepszymi rozwiązaniami opisanymi tutaj.
Nuta
Jeśli uzyskujesz dostęp do dwóch zasobów direct2D Direct2D utworzonych z dwóch różnych fabryk Direct2D, nie powoduje konfliktów dostępu, o ile podstawowe Direct3D urządzenia i konteksty urządzeń są również różne. Mówiąc o "uzyskiwaniu dostępu do zasobów Direct2D" w tym artykule, oznacza to naprawdę "uzyskiwanie dostępu do zasobów Direct2D utworzonych na podstawie tego samego urządzenia Direct2D", chyba że określono inaczej.
Tworzenie aplikacji Thread-Safe wywołujących tylko interfejsy API Direct2D
Możesz utworzyć wielowątkowe wystąpienie fabryki Direct2D. Można używać i udostępniać fabrykę wielowątkowa oraz wszystkie jej zasoby z więcej niż jednego wątku, ale dostęp do tych zasobów (za pośrednictwem wywołań Direct2D) jest serializowany przez direct2D, więc nie występują konflikty dostępu. Jeśli aplikacja wywołuje tylko interfejsy API Direct2D, taka ochrona jest automatycznie wykonywana przez funkcję Direct2D na poziomie szczegółowym z minimalnym obciążeniem. Kod umożliwiający utworzenie fabryki wielowątkowej w tym miejscu.
ID2D1Factory* m_D2DFactory;
// Create a Direct2D factory.
HRESULT hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_MULTI_THREADED,
&m_D2DFactory
);
Na poniższym obrazie pokazano, jak Direct2D serializuje dwa wątki, które tworzą wywołania przy użyciu tylko interfejsu API Direct2D.
Tworzenie aplikacji Thread-Safe Direct2D z minimalnymi wywołaniami Direct3D lub DXGI
Często aplikacja Direct2D wykonuje również wywołania Direct3D lub DXGI. Na przykład wątek wyświetlania będzie rysowany w trybie Direct2D przy użyciu łańcuch wymiany DXGI.
W takim przypadku zapewnienie bezpieczeństwa wątków jest bardziej skomplikowane: niektóre Direct2D wywołuje pośrednio dostęp do podstawowych zasobów Direct3D, które mogą być jednocześnie dostępne przez inny wątek, który wywołuje direct3D lub DXGI. Ponieważ te wywołania Direct3D lub DXGI są poza świadomością i kontrolą Direct2D, należy utworzyć wielowątkową fabrykę Direct2D, ale należy wykonać mor, aby uniknąć konfliktów dostępu.
Na tym diagramie przedstawiono konflikt dostępu Direct3D zasobów z powodu wątku T0 uzyskiwania dostępu do zasobu pośrednio za pośrednictwem wywołania Direct2D Direct2D i T2 uzyskiwania dostępu do tego samego zasobu bezpośrednio za pośrednictwem wywołania Direct3D lub DXGI.
Nuta
Ochrona wątków, która direct2D zapewnia (niebieska blokada na tym obrazie) nie pomaga w tym przypadku.
Aby uniknąć konfliktu dostępu do zasobów, zalecamy jawne uzyskanie blokady, która Direct2D używa na potrzeby synchronizacji dostępu wewnętrznego, i stosować tę blokadę, gdy wątek musi wykonać Direct3D lub wywołania DXGI, które mogą powodować konflikt dostępu, jak pokazano tutaj. W szczególności należy zachować szczególną ostrożność w kodzie, który używa wyjątków lub wczesnego systemu opartego na kodach powrotnych HRESULT. Z tego powodu zalecamy użycie wzorca RAII (pozyskiwanie zasobów jest inicjowaniem), aby wywołać metodę Enter i Leave.
Nuta
Ważne jest, aby sparować wywołania do Enter i Pozostaw metody, w przeciwnym razie aplikacja może zakleszczeć.
W tym miejscu kod przedstawia przykład blokowania, a następnie odblokowywania wokół wywołań Direct3D lub 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();
}
Nuta
Niektóre wywołania Direct3D lub DXGI (w szczególności IDXGISwapChain::P resent) mogą uzyskiwać blokady i/lub wyzwalać wywołania zwrotne w kodzie funkcji wywołującej lub metody. Należy pamiętać o tym i upewnić się, że takie zachowanie nie powoduje zakleszczeń. Aby uzyskać więcej informacji, zobacz temat omówienie DXGI.
W przypadku używania metod Enter i Leave wywołania są chronione przez automatyczne Direct2D i jawną blokadę, dzięki czemu aplikacja nie osiągnie konfliktu dostępu.
Istnieją inne podejścia do obejścia tego problemu. Zalecamy jednak jawne zabezpieczenie wywołań Direct3D lub DXGI za pomocą blokady Direct2D Direct2D, ponieważ zwykle zapewnia lepszą wydajność, ponieważ chroni współbieżność na znacznie bardziej precyzyjnym poziomie i z niższym obciążeniem w ramach pokrycia Direct2D.
Zapewnianie niepodzielności operacji stanowych
Chociaż funkcje bezpieczeństwa wątków DirectX mogą pomóc w zapewnieniu, że żadne dwa pojedyncze wywołania interfejsu API nie są wykonywane współbieżnie, należy również upewnić się, że wątki, które tworzą stanowe wywołania interfejsu API, nie zakłócają siebie. Oto przykład.
- Istnieją dwa wiersze tekstu, które mają być renderowane zarówno na ekranie (według wątku 0), jak i poza ekranem (według wątku 1): Wiersz 1 to "A jest większa", a linia 2 to "niż B", z których oba zostaną narysowane przy użyciu solidnego czarnego pędzla.
- Wątek 1 rysuje pierwszy wiersz tekstu.
- Wątek 0 reaguje na dane wejściowe użytkownika, aktualizuje oba wiersze tekstu na "B jest mniejszy" i "niż A" odpowiednio, i zmienił kolor pędzla na czerwony dla własnego rysunku;
- Wątek 1 kontynuuje rysowanie drugiego wiersza tekstu, który jest teraz "niż A", z czerwonym pędzlem kolorów;
- Na koniec otrzymujemy dwa wiersze tekstu w obiekcie docelowym rysunku poza ekranem: "A jest większe" w kolorze czarnym i "niż A" na czerwono.
W górnym wierszu wątek 0 rysuje bieżące ciągi tekstowe i bieżący pędzl. Wątek 1 kończy tylko rysunek poza ekranem w górnej połowie.
W środkowym wierszu wątek 0 reaguje na interakcję użytkownika, aktualizuje ciągi tekstowe i szczotkę, a następnie odświeża ekran. W tym momencie wątek 1 jest zablokowany. W dolnym wierszu ostateczne renderowanie poza ekranem po wznowieniu rysowania dolnej połowy ze zmienionym pędzlem i zmienionym ciągiem tekstowym.
Aby rozwiązać ten problem, zalecamy posiadanie oddzielnego kontekstu dla każdego wątku, aby:
- Należy utworzyć kopię kontekstu urządzenia, aby zasoby modyfikowalne (tj. zasoby, które mogą się różnić podczas wyświetlania lub drukowania, takie jak zawartość tekstowa lub szczotka koloru stałego w przykładzie), nie zmieniają się podczas renderowania. W tym przykładzie należy zachować kopię tych dwóch wierszy tekstów i pędzla kolorów przed narysowaniem. Dzięki temu gwarantujesz, że każdy wątek ma kompletną i spójną zawartość do rysowania i prezentowania.
- Należy udostępniać zasoby o dużej wadze (takie jak mapy bitowe i złożone wykresy efektów), które są inicjowane raz, a następnie nigdy nie modyfikowane między wątkami w celu zwiększenia wydajności.
- Możesz udostępniać zasoby o lekkiej wadze (takie jak pędzle z kolorami stałymi i formaty tekstu), które są inicjowane raz, a następnie nigdy nie są modyfikowane między wątkami lub nie
Streszczenie
Podczas tworzenia wielowątkowych aplikacji Direct2D należy utworzyć wielowątkową fabrykę Direct2D, a następnie uzyskać wszystkie zasoby Direct2D z tej fabryki. Jeśli wątek wykona wywołania Direct3D lub DXGI, należy również jawnie uzyskać blokadę Direct2D, aby chronić te wywołania Direct3D lub DXGI. Ponadto należy zapewnić integralność kontekstu, mając kopię zasobów modyfikowalnych dla każdego wątku.