다중 스레드 Direct2D 앱
Direct2D 앱을 개발하는 경우 둘 이상의 스레드에서 Direct2D 리소스에 액세스해야 할 수 있습니다. 다른 경우에는 다중 스레딩을 사용하여 성능 향상 또는 응답성 향상을 원할 수 있습니다(예: 화면 표시에 스레드 1개, 오프라인 렌더링을 위해 별도의 스레드 사용).
이 항목에서는 Direct3D 렌더링이 거의 또는 전혀 없는 다중 스레드 Direct2D 앱을 개발하기 위한 모범 사례에 대해 설명합니다. 동시성 문제로 인한 소프트웨어 결함은 추적하기 어려울 수 있으며, 다중 스레딩 정책을 계획하고 여기에 설명된 모범 사례를 따르는 것이 유용합니다.
참고
두 개의 서로 다른 단일 스레드 Direct2D 팩터리에서 만든 두 개의 Direct2D 리소스에 액세스하는 경우 기본 Direct3D 디바이스 및 디바이스 컨텍스트도 고유하기만 하면 액세스 충돌이 발생하지 않습니다. 이 문서에서 "Direct2D 리소스에 액세스"에 대해 이야기할 때 달리 명시되지 않는 한 실제로 "동일한 Direct2D 디바이스에서 만든 Direct2D 리소스에 액세스"를 의미합니다.
Direct2D API만 호출하는 Thread-Safe 앱 개발
다중 스레드 Direct2D 팩터리 instance 만들 수 있습니다. 다중 스레드 팩터리와 둘 이상의 스레드에서 모든 리소스를 사용하고 공유할 수 있지만 Direct2D 호출을 통해 해당 리소스에 대한 액세스는 Direct2D에 의해 직렬화되므로 액세스 충돌이 발생하지 않습니다. 앱이 Direct2D API만 호출하는 경우 이러한 보호는 최소 오버헤드가 있는 세분화된 수준에서 Direct2D에 의해 자동으로 수행됩니다. 여기에서 다중 스레드 팩터리를 만드는 코드입니다.
ID2D1Factory* m_D2DFactory;
// Create a Direct2D factory.
HRESULT hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_MULTI_THREADED,
&m_D2DFactory
);
이 이미지는 Direct2D가 Direct2D API만 사용하여 호출하는 두 스레드를 직렬화하는 방법을 보여 줍니다.
최소 Direct3D 또는 DXGI 호출을 사용하여 Thread-Safe Direct2D 앱 개발
Direct2D 앱이 일부 Direct3D 또는 DXGI 호출을 하는 경우가 많습니다. 예를 들어 디스플레이 스레드는 Direct2D에서 그린 다음 DXGI 스왑 체인을 사용하여 표시됩니다.
이 경우 스레드 안전 보장이 더 복잡합니다. 일부 Direct2D 호출은 Direct3D 또는 DXGI를 호출하는 다른 스레드에서 동시에 액세스할 수 있는 기본 Direct3D 리소스에 간접적으로 액세스합니다. 이러한 Direct3D 또는 DXGI 호출이 Direct2D의 인식 및 제어를 벗어나기 때문에 다중 스레드 Direct2D 팩터리를 만들어야 하지만 액세스 충돌을 방지하려면 mor를 수행해야 합니다.
이 다이어그램은 Direct2D 호출을 통해 간접적으로 리소스에 액세스하는 스레드 T0 및 Direct3D 또는 DXGI 호출을 통해 동일한 리소스에 직접 액세스하는 T2로 인한 Direct3D 리소스 액세스 충돌을 보여줍니다.
참고
이 경우 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 및 명시적 잠금으로 보호되므로 앱이 액세스 충돌에 충돌하지 않습니다.
이 문제를 해결하는 다른 방법이 있습니다. 그러나 Direct2D 잠금을 사용하여 Direct3D 또는 DXGI 호출을 명시적으로 보호하는 것이 좋습니다. 일반적으로 Direct2D 의 커버에서 훨씬 더 미세한 수준과 낮은 오버헤드로 동시성을 보호하므로 더 나은 성능을 제공하기 때문입니다.
상태 저장 작업의 원자성 보장
DirectX의 스레드 안전 기능은 두 개의 개별 API 호출이 동시에 수행되지 않도록 하는 데 도움이 될 수 있지만 상태 저장 API 호출을 수행하는 스레드가 서로 간섭하지 않도록 해야 합니다. 다음은 예제입니다.
- 화면(스레드 0 기준) 및 화면 외(스레드 1 기준)에 렌더링하려는 텍스트 줄이 두 개 있습니다. 줄 #1은 "A가 더 큼"이고 줄 #2는 "B보다 큼"이며, 둘 다 단색 검은색 브러시를 사용하여 그려집니다.
- 스레드 1은 텍스트의 첫 번째 줄을 그립니다.
- 스레드 0은 사용자 입력에 반응하고, 두 텍스트 줄을 각각 "B가 더 작음"과 "A보다 작음"으로 업데이트하고, 브러시 색을 자체 그리기에 대해 단색 빨간색으로 변경했습니다.
- 스레드 1은 빨간색 브러시를 사용하여 이제 "A보다"인 텍스트의 두 번째 줄을 계속 그립니다.
- 마지막으로, 화면 외 그리기 대상에 두 줄의 텍스트가 표시됩니다. "A는 검은색으로 더 큼", 빨간색은 "A보다 큼"입니다.
위쪽 행에서 스레드 0은 현재 텍스트 문자열과 현재 검은색 브러시로 그립니다. 스레드 1은 위쪽 절반에 화면 오프 스크린 그리기만 완료합니다.
가운데 행에서 스레드 0은 사용자 상호 작용에 응답하고 텍스트 문자열과 브러시를 업데이트한 다음 화면을 새로 고칩니다. 이 시점에서 스레드 1이 차단됩니다. 맨 아래 행에서 스레드 1 이후의 마지막 화면 외 렌더링은 변경된 브러시와 변경된 텍스트 문자열을 사용하여 아래쪽 절반을 그리는 것을 다시 시작합니다.
이 문제를 해결하려면 다음과 같이 각 스레드에 대해 별도의 컨텍스트를 사용하는 것이 좋습니다.
- 렌더링할 때 변경 가능한 리소스(예: 텍스트 내용 또는 예제의 단색 브러시와 같이 표시 또는 인쇄 중에 달라질 수 있는 리소스)가 변경되지 않도록 디바이스 컨텍스트의 복사본을 만들어야 합니다. 이 샘플에서는 그리기 전에 두 줄의 텍스트와 색 브러시의 복사본을 유지해야 합니다. 이렇게 하면 각 스레드에 그리고 표시할 수 있는 완전하고 일관된 콘텐츠가 보장됩니다.
- 성능 향상을 위해 스레드 간에 한 번 초기화되고 수정되지 않는 무거운 리소스(예: 비트맵 및 복잡한 효과 그래프)를 공유해야 합니다.
- 한 번 초기화되고 스레드 간에 수정되지 않는 경량 리소스(예: 단색 브러시 및 텍스트 형식)를 공유할 수 있습니다.
요약
다중 스레드 Direct2D 앱을 개발할 때 다중 스레드 Direct2D 팩터리를 만든 다음 해당 팩터리에서 모든 Direct2D 리소스를 파생해야 합니다. 스레드가 Direct3D 또는 DXGI 호출을 수행하는 경우 Direct3D 또는 DXGI 호출을 보호하려면 Direct2D 잠금을 명시적으로 획득한 다음 적용해야 합니다. 또한 각 스레드에 대해 변경 가능한 리소스의 복사본을 사용하여 컨텍스트 무결성을 보장해야 합니다.