다음을 통해 공유


다중 스레드 Direct2D 앱

Direct2D 앱을 개발하는 경우 둘 이상의 스레드에서 Direct2D 리소스에 액세스해야 할 수 있습니다. 다른 경우에는 다중 스레딩을 사용하여 성능이 향상되거나 응답성이 향상되는 경우가 있습니다(예: 화면 표시에 스레드 1개, 오프라인 렌더링을 위해 별도의 스레드 사용).

이 항목에서는 Direct3D 렌더링이 거의 또는 전혀 없는 다중 스레드 Direct2D 앱을 개발하기 위한 모범 사례를 설명합니다. 동시성 문제로 인한 소프트웨어 결함은 추적하기 어려울 수 있으며, 다중 스레딩 정책을 계획하고 여기에 설명된 모범 사례를 따르는 것이 유용합니다.

메모

두 개의 서로 다른 단일 스레드 Direct2D 팩터리에서 만든 두 개의 Direct2D 리소스에 액세스하는 경우 기본 Direct3D 디바이스 및 디바이스 컨텍스트도 구별되는 한 액세스 충돌이 발생하지 않습니다. 이 문서에서 "Direct2D 리소스 액세스"에 대해 이야기할 때 달리 명시하지 않는 한 실제로 "동일한 Direct2D 디바이스에서 만든 Direct2D 리소스에 액세스"를 의미합니다.

Direct2D API만 호출하는 Thread-Safe 앱 개발

다중 스레드 Direct2D 팩터리 인스턴스를 만들 수 있습니다. 다중 스레드 팩터리와 둘 이상의 스레드에서 모든 리소스를 사용하고 공유할 수 있지만 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 호출하고 나가기 메서드를 호출하는 것이 좋습니다.

메모

Enter 호출을 페어링하고 메서드를 종료해야 합니다. 그렇지 않으면 앱이 교착 상태에 빠질 수 있습니다.

 

이 코드는 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지원)은 호출 함수 또는 메서드의 코드에 대한 잠금 및/또는 트리거 콜백을 획득할 수 있습니다. 이를 알고 있어야 하며 이러한 동작으로 인해 교착 상태가 발생하지 않도록 해야 합니다. 자세한 내용은 DXGI 개요 항목을 참조하세요.

 

direct2d 및 direct3d 스레드 잠금 다이어그램을 .

EnterLeave 메서드를 사용하면 자동 Direct2D 및 명시적 잠금으로 호출이 보호되므로 앱이 액세스 충돌을 발생하지 않습니다.

이 문제를 해결하는 다른 방법이 있습니다. 그러나 Direct3D 또는 DXGI 호출을 Direct2D 잠금으로 명시적으로 보호하는 것이 좋습니다. 일반적으로 Direct2D의 덮개 아래에서 훨씬 더 세밀한 수준에서 낮은 오버헤드로 동시성을 보호하므로 성능이 향상되기 때문입니다.

상태 저장 작업의 원자성 보장

DirectX 스레드 보안 기능은 두 개의 개별 API 호출이 동시에 수행되지 않도록 하는 데 도움이 될 수 있지만 상태 저장 API 호출을 수행하는 스레드가 서로 간섭하지 않도록 해야 합니다. 다음은 예제입니다.

  1. 화면(스레드 0) 및 화면 외(스레드 1 기준)에 렌더링하려는 텍스트의 두 줄이 있습니다. 줄 #1은 "A가 더 큽니다", #2 줄은 "B보다 큼"이며, 둘 다 단색 검은색 브러시를 사용하여 그려집니다.
  2. 스레드 1은 텍스트의 첫 번째 줄을 그립니다.
  3. 스레드 0은 사용자 입력에 반응하고, 두 텍스트 줄을 각각 "B가 작음"과 "A보다 작음"으로 업데이트하고, 브러시 색을 자체 드로잉에 대해 단색 빨간색으로 변경했습니다.
  4. 스레드 1은 빨간색 브러시를 사용하여 이제 "A보다"인 두 번째 텍스트 줄을 계속 그립니다.
  5. 마지막으로, 화면 외 그리기 대상에 "A는 검은색으로 더 큼", 빨간색은 "A보다 큼"이라는 두 줄의 텍스트를 가져옵니다.

화상 스레드 및 오프 화면 스레드의 다이어그램을 .

위쪽 행에서 스레드 0은 현재 텍스트 문자열과 현재 검은색 브러시로 그립니다. 스레드 1은 위쪽 절반에 화면 오프 스크린 그리기만 완료합니다.

가운데 행에서 스레드 0은 사용자 상호 작용에 응답하고 텍스트 문자열과 브러시를 업데이트한 다음 화면을 새로 고칩니다. 이 시점에서 스레드 1이 차단됩니다. 맨 아래 행에서 스레드 1 이후의 마지막 화면 끄기 렌더링은 변경된 브러시 및 변경된 텍스트 문자열을 사용하여 아래쪽 절반을 그리는 것을 다시 시작합니다.

이 문제를 해결하려면 다음과 같이 각 스레드에 대해 별도의 컨텍스트를 사용하는 것이 좋습니다.

  • 렌더링할 때 변경 가능한 리소스(예: 텍스트 내용 또는 예제의 단색 브러시와 같이 표시 또는 인쇄 중에 달라질 수 있는 리소스)가 변경되지 않도록 디바이스 컨텍스트의 복사본을 만들어야 합니다. 이 샘플에서는 그리기 전에 두 줄의 텍스트와 색 브러시의 복사본을 유지해야 합니다. 이렇게 하면 각 스레드에 그리기 및 표시할 수 있는 완전하고 일관된 콘텐츠가 보장됩니다.
  • 성능 향상을 위해 스레드 간에 한 번 초기화된 후 수정되지 않는 무거운 리소스(예: 비트맵 및 복잡한 효과 그래프)를 공유해야 합니다.
  • 한 번 초기화되고 스레드 간에 수정되지 않는 경량 리소스(예: 단색 브러시 및 텍스트 형식)를 공유할 수 있습니다.

요약

다중 스레드 Direct2D 앱을 개발하는 경우 다중 스레드 Direct2D 팩터리를 만든 다음 해당 팩터리에서 모든 Direct2D 리소스를 파생해야 합니다. 스레드가 Direct3D 또는 DXGI 호출을 경우 Direct2D 잠금을 명시적으로 획득한 다음 Direct3D 또는 DXGI 호출을 보호하도록 적용해야 합니다. 또한 각 스레드에 대해 변경 가능한 리소스의 복사본을 사용하여 컨텍스트 무결성을 보장해야 합니다.