WPF 및 Direct3D9 상호 운용성

WPF(Windows Presentation Foundation) 애플리케이션에 Direct3D9 콘텐츠를 포함할 수 있습니다. 이 항목에서는 WPF와 효율적으로 상호 운용되도록 Direct3D9 콘텐츠를 만드는 방법을 설명합니다.

참고

WPF에서 Direct3D9 콘텐츠를 사용하는 경우 성능에 대해서도 고려해야 합니다. 성능을 최적화하는 방법에 대한 자세한 내용은 Direct3D9 및 WPF 상호 운용성에 대한 성능 고려 사항을 참조하세요.

버퍼 표시

D3DImage 클래스는 백 버퍼프런트 버퍼라고 하는 두 개의 디스플레이 버퍼를 관리합니다. 백 버퍼는 Direct3D9 표면입니다. Unlock 메서드를 호출할 때 백 버퍼에 대한 변경 내용이 프런트 버퍼로 복사됩니다.

다음 그림에서는 백 버퍼와 프런트 버퍼 간의 관계를 보여 줍니다.

D3DImage 표시 버퍼

Direct3D9 디바이스 만들기

Direct3D9 콘텐츠를 렌더링하려면 Direct3D9 디바이스를 만들어야 합니다. 디바이스를 만드는 데 사용할 수 있는 두 개의 Direct3D9 개체(IDirect3D9IDirect3D9Ex)가 있습니다. 이러한 개체를 사용하여 각각 IDirect3DDevice9IDirect3DDevice9Ex 디바이스를 만듭니다.

다음 메서드 중 하나를 호출하여 디바이스를 만듭니다.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

  • HRESULT Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex **ppD3D);

Windows Vista 이상의 운영 체제에서는 WDDM(Windows 디스플레이 드라이버 모델)을 사용하도록 구성된 디스플레이에서 Direct3DCreate9Ex 메서드를 사용합니다. 다른 플랫폼에서 Direct3DCreate9 메서드를 사용합니다.

Direct3DCreate9Ex 메서드의 가용성

d3d9.dll은 Windows Vista 이상 운영 체제에서만 Direct3DCreate9Ex 메서드를 사용합니다. Windows XP에서 함수를 직접 연결하면 애플리케이션이 로드되지 않습니다. Direct3DCreate9Ex 메서드가 지원되는지 여부를 확인하려면 DLL을 로드하고 proc 주소를 찾습니다. 다음 코드에서는 Direct3DCreate9Ex 메서드를 테스트하는 방법을 보여 줍니다. 전체 코드 예제는 연습: WPF에서 호스팅을 위한 Direct3D9 콘텐츠 만들기를 참조하세요.

HRESULT
CRendererManager::EnsureD3DObjects()
{
    HRESULT hr = S_OK;

    HMODULE hD3D = NULL;
    if (!m_pD3D)
    {
        hD3D = LoadLibrary(TEXT("d3d9.dll"));
        DIRECT3DCREATE9EXFUNCTION pfnCreate9Ex = (DIRECT3DCREATE9EXFUNCTION)GetProcAddress(hD3D, "Direct3DCreate9Ex");
        if (pfnCreate9Ex)
        {
            IFC((*pfnCreate9Ex)(D3D_SDK_VERSION, &m_pD3DEx));
            IFC(m_pD3DEx->QueryInterface(__uuidof(IDirect3D9), reinterpret_cast<void **>(&m_pD3D)));
        }
        else
        {
            m_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
            if (!m_pD3D) 
            {
                IFC(E_FAIL);
            }
        }

        m_cAdapters = m_pD3D->GetAdapterCount();
    }

Cleanup:
    if (hD3D)
    {
        FreeLibrary(hD3D);
    }

    return hr;
}

HWND 만들기

디바이스를 만들려면 HWND가 필요합니다. 일반적으로 사용할 Direct3D9용 더미 HWND를 만듭니다. 다음 코드 예제에서는 더미 HWND를 만드는 방법을 보여줍니다.

HRESULT
CRendererManager::EnsureHWND()
{
    HRESULT hr = S_OK;

    if (!m_hwnd)
    {
        WNDCLASS wndclass;

        wndclass.style = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = DefWindowProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = 0;
        wndclass.hInstance = NULL;
        wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
        wndclass.lpszMenuName = NULL;
        wndclass.lpszClassName = szAppName;

        if (!RegisterClass(&wndclass))
        {
            IFC(E_FAIL);
        }

        m_hwnd = CreateWindow(szAppName,
                            TEXT("D3DImageSample"),
                            WS_OVERLAPPEDWINDOW,
                            0,                   // Initial X
                            0,                   // Initial Y
                            0,                   // Width
                            0,                   // Height
                            NULL,
                            NULL,
                            NULL,
                            NULL);
    }

Cleanup:
    return hr;
}

매개 변수 표시

디바이스를 만들려면 D3DPRESENT_PARAMETERS 구조체도 필요하지만 몇 개의 매개 변수만 중요합니다. 이러한 매개 변수는 메모리 공간을 최소화하기 위해 선택됩니다.

BackBufferHeightBackBufferWidth 필드를 1로 설정합니다. 0으로 설정하면 HWND의 차원으로 설정됩니다.

Direct3D9에서 사용하는 메모리 손상을 방지하고 Direct3D9가 FPU 설정을 변경하지 못하도록 항상 설정 D3DCREATE_MULTITHREADEDD3DCREATE_FPU_PRESERVE 플래그를 설정합니다.

다음 코드에서는 D3DPRESENT_PARAMETERS 구조체를 초기화하는 방법을 보여줍니다.

HRESULT 
CRenderer::Init(IDirect3D9 *pD3D, IDirect3D9Ex *pD3DEx, HWND hwnd, UINT uAdapter)
{
    HRESULT hr = S_OK;

    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = TRUE;
    d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
    d3dpp.BackBufferHeight = 1;
    d3dpp.BackBufferWidth = 1;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

    D3DCAPS9 caps;
    DWORD dwVertexProcessing;
    IFC(pD3D->GetDeviceCaps(uAdapter, D3DDEVTYPE_HAL, &caps));
    if ((caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) == D3DDEVCAPS_HWTRANSFORMANDLIGHT)
    {
        dwVertexProcessing = D3DCREATE_HARDWARE_VERTEXPROCESSING;
    }
    else
    {
        dwVertexProcessing = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
    }

    if (pD3DEx)
    {
        IDirect3DDevice9Ex *pd3dDevice = NULL;
        IFC(pD3DEx->CreateDeviceEx(
            uAdapter,
            D3DDEVTYPE_HAL,
            hwnd,
            dwVertexProcessing | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
            &d3dpp,
            NULL,
            &m_pd3dDeviceEx
            ));

        IFC(m_pd3dDeviceEx->QueryInterface(__uuidof(IDirect3DDevice9), reinterpret_cast<void**>(&m_pd3dDevice)));  
    }
    else 
    {
        assert(pD3D);

        IFC(pD3D->CreateDevice(
            uAdapter,
            D3DDEVTYPE_HAL,
            hwnd,
            dwVertexProcessing | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
            &d3dpp,
            &m_pd3dDevice
            ));
    }

Cleanup:
    return hr;
}

백 버퍼 렌더링 대상 만들기

D3DImage에 Direct3D9 콘텐츠를 표시하려면 Direct3D9 화면을 만들고 SetBackBuffer 메서드를 호출하여 할당합니다.

어댑터 지원 확인

표면을 만들기 전에 모든 어댑터가 필요한 표면 속성을 지원하는지 확인합니다. 하나의 어댑터로만 렌더링하더라도 WPF 창이 시스템의 모든 어댑터에 표시될 수 있습니다. 항상 다중 어댑터 구성을 처리하는 Direct3D9 코드를 작성해야 하며 WPF가 사용 가능한 어댑터 간에 표면을 이동할 수 있으므로 모든 어댑터에서 지원을 확인해야 합니다.

다음 코드 예제에서는 시스템의 모든 어댑터에서 Direct3D9 지원을 확인하는 방법을 보여 줍니다.

HRESULT
CRendererManager::TestSurfaceSettings()
{
    HRESULT hr = S_OK;

    D3DFORMAT fmt = m_fUseAlpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;

    // 
    // We test all adapters because because we potentially use all adapters.
    // But even if this sample only rendered to the default adapter, you
    // should check all adapters because WPF may move your surface to
    // another adapter for you!
    //

    for (UINT i = 0; i < m_cAdapters; ++i)
    {
        // Can we get HW rendering?
        IFC(m_pD3D->CheckDeviceType(
            i,
            D3DDEVTYPE_HAL,
            D3DFMT_X8R8G8B8,
            fmt,
            TRUE
            )); 

        // Is the format okay?
        IFC(m_pD3D->CheckDeviceFormat(
            i,
            D3DDEVTYPE_HAL,
            D3DFMT_X8R8G8B8,
            D3DUSAGE_RENDERTARGET | D3DUSAGE_DYNAMIC, // We'll use dynamic when on XP
            D3DRTYPE_SURFACE,
            fmt
            ));

        // D3DImage only allows multisampling on 9Ex devices. If we can't 
        // multisample, overwrite the desired number of samples with 0.
        if (m_pD3DEx && m_uNumSamples > 1)
        {   
            assert(m_uNumSamples <= 16);

            if (FAILED(m_pD3D->CheckDeviceMultiSampleType(
                i,
                D3DDEVTYPE_HAL,
                fmt,
                TRUE,
                static_cast<D3DMULTISAMPLE_TYPE>(m_uNumSamples),
                NULL
                )))
            {
                m_uNumSamples = 0;
            }
        }
        else
        {
            m_uNumSamples = 0;
        }
    }

Cleanup:
    return hr;
}

Surface 만들기

표면을 만들기 전에 디바이스 기능이 대상 운영 체제에서 좋은 성능을 지원하는지 확인합니다. 자세한 내용은 Direct3D9 및 WPF 상호 운용성을 위한 성능 고려 사항을 참조하세요.

디바이스 기능을 확인한 경우 표면을 만들 수 있습니다. 다음 코드 예제에서는 랜더링 대상을 만드는 방법을 보여줍니다.

HRESULT
CRenderer::CreateSurface(UINT uWidth, UINT uHeight, bool fUseAlpha, UINT m_uNumSamples)
{
    HRESULT hr = S_OK;

    SAFE_RELEASE(m_pd3dRTS);

    IFC(m_pd3dDevice->CreateRenderTarget(
        uWidth,
        uHeight,
        fUseAlpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8,
        static_cast<D3DMULTISAMPLE_TYPE>(m_uNumSamples),
        0,
        m_pd3dDeviceEx ? FALSE : TRUE,  // Lockable RT required for good XP perf
        &m_pd3dRTS,
        NULL
        ));

    IFC(m_pd3dDevice->SetRenderTarget(0, m_pd3dRTS));

Cleanup:
    return hr;
}

WDDM

WDDM을 사용하도록 구성된 Windows Vista 이상의 운영 체제에서는 렌더링 대상 텍스처를 만들고 수준 0 표면을 SetBackBuffer 메서드에 전달할 수 있습니다. 잠금 가능한 렌더링 대상 텍스처를 만들 수 없고 성능이 저하되므로 이 방식은 Windows XP에서는 권장되지 않습니다.

디바이스 상태 처리

D3DImage 클래스는 백 버퍼프런트 버퍼라고 하는 두 개의 디스플레이 버퍼를 관리합니다. 백 버퍼는 Direct3D 표면입니다. 백 버퍼에 대한 변경 내용은 Unlock 메서드를 호출할 때 프런트 버퍼로 복사되며, 여기서 메서드는 하드웨어에 표시됩니다. 경우에 따라 프런트 버퍼를 사용할 수 없게 됩니다. 화면 잠금, Direct3D 애플리케이션을 제외 하는 전체 화면, 사용자 전환 또는 기타 시스템 활동과이 부족 한 가용성을 발생할 수 있습니다. WPF 애플리케이션을 처리 하 여 알려집니다 이런 경우는 IsFrontBufferAvailableChanged 이벤트입니다. 애플리케이션이 사용할 수 없게 되는 프런트 버퍼가에 반응 하는 방법을 소프트웨어 렌더링으로 대체 WPF 사용 여부에 따라 달라 집니다. 이 SetBackBuffer 메서드에는 WPF가 소프트웨어 렌더링으로 대체되는지 여부를 지정하는 매개 변수를 사용하는 오버로드가 있습니다.

SetBackBuffer(D3DResourceType, IntPtr) 오버로드를 호출하거나 enableSoftwareFallback 매개 변수가 false로 설정된 SetBackBuffer(D3DResourceType, IntPtr, Boolean) 오버로드를 호출하면 렌더링 시스템은 전면 버퍼를 사용할 수 없게 되고 아무것도 표시되지 않으면 백 버퍼에 대한 참조를 해제합니다. 프런트 버퍼가 다시 제공 되 면 렌더링 발생을 IsFrontBufferAvailableChanged WPF 애플리케이션에 알리는 이벤트입니다. IsFrontBufferAvailableChanged 이벤트에 대한 이벤트 처리기를 만들어 유효한 Direct3D 표면을 사용하여 렌더링을 다시 시작할 수 있습니다. 렌더링을 다시 시작하려면 SetBackBuffer를 호출해야 합니다.

enableSoftwareFallback 매개 변수가 true로 설정된 SetBackBuffer(D3DResourceType, IntPtr, Boolean) 오버로드를 호출하는 경우 렌더링 시스템은 프런트 버퍼를 사용할 수 없게 되면 백 버퍼에 대한 참조를 유지하므로 프런트 버퍼를 다시 사용할 수 있을 때 SetBackBuffer를 호출할 필요가 없습니다.

소프트웨어 렌더링을 사용하도록 설정하면 사용자의 디바이스를 사용할 수 없게 되지만 렌더링 시스템에서 Direct3D 화면에 대한 참조를 유지하는 경우가 있을 수 있습니다. Direct3D9 디바이스를 사용할 수 없는지 확인하려면 TestCooperativeLevel 메서드를 호출합니다. Direct3D9Ex 디바이스를 확인하려면 TestCooperativeLevel 메서드가 더 이상 사용되지 않고 항상 성공을 반환하기 때문에 CheckDeviceState 메서드를 호출합니다. 사용자 디바이스를 사용할 수 없게 되면 SetBackBuffer를 호출하여 백 버퍼에 대한 WPF의 참조를 해제합니다. 디바이스를 재설정 해야 할 경우 호출 SetBackBuffer 사용 하 여는 backBuffer 매개 변수 설정 null, 다음 호출 SetBackBuffer 사용 하 여 다시 backBuffer 유효한 Direct3D 화면을로 설정 합니다.

다중 어댑터 지원을 구현하는 경우에만 Reset 메서드를 호출하여 잘못된 디바이스에서 복구합니다. 그렇지 않으면 모든 Direct3D9 인터페이스를 해제하고 완전히 다시 만듭니다. 어댑터 레이아웃이 변경된 경우 변경 전에 만든 Direct3D9 개체는 업데이트되지 않습니다.

크기 조정 처리

D3DImage가 네이티브 크기가 아닌 해상도로 표시되는 경우 BilinearFant로 대체된다는 점을 제외하고 현재 BitmapScalingMode에 따라 크기가 조정됩니다.

더 높은 충실도가 필요한 경우 D3DImage의 컨테이너 크기가 변경되면 새 표면을 만들어야 합니다.

크기 조정을 처리할 수 있는 세 가지 방식이 있습니다.

  • 레이아웃 시스템에 참여하고 크기가 변경되면 새 표면을 만듭니다. 비디오 메모리를 소모하거나 조각화할 수 있으므로 너무 많은 표면을 만들지 마세요.

  • 크기 조정 이벤트가 고정된 기간 동안 발생하지 않을 때까지 기다렸다가 새 표면을 만듭니다.

  • 컨테이너 차원을 초당 여러 번 확인하는 DispatcherTimer를 만듭니다.

다중 모니터 최적화

렌더링 시스템이 D3DImage를 다른 모니터로 이동할 때 성능이 크게 저하될 수 있습니다.

WDDM에서는 모니터가 동일한 비디오 카드에 있고 Direct3DCreate9Ex를 사용하는 한 성능이 저하되지 않습니다. 모니터가 별도의 비디오 카드에 있으면 성능이 저하됩니다. Windows XP에서는 항상 성능이 저하됩니다.

D3DImage가 다른 모니터로 이동하면 해당 어댑터에 새 표면을 만들어 좋은 성능을 복원할 수 있습니다.

성능 저하를 방지하려면 다중 모니터 사례에 대한 코드를 특별히 작성합니다. 다음 목록에서는 다중 모니터 코드를 작성하는 한 가지 방법을 보여줍니다.

  1. Visual.ProjectToScreen 메서드를 사용하여 화면 공간에서 D3DImage의 포인트를 찾습니다.

  2. MonitorFromPoint GDI 메서드를 사용하여 포인트를 표시하는 모니터를 찾습니다.

  3. IDirect3D9::GetAdapterMonitor 메서드를 사용하여 모니터에 있는 Direct3D9 어댑터를 찾습니다.

  4. 어댑터가 백 버퍼가 있는 어댑터와 동일하지 않은 경우 새 모니터에 새 백 버퍼를 만들고 D3DImage 백 버퍼에 할당합니다.

참고

D3DImage 스트래들 모니터의 경우 동일한 어댑터의 WDDM 및 IDirect3D9Ex의 경우를 제외하고 성능이 느려집니다. 이 상황에서는 성능을 향상시킬 수 있는 방법은 없습니다.

다음 코드 예제에서는 현재 모니터를 찾는 방법을 보여 줍니다.

void 
CRendererManager::SetAdapter(POINT screenSpacePoint)
{
    CleanupInvalidDevices();

    //
    // After CleanupInvalidDevices, we may not have any D3D objects. Rather than
    // recreate them here, ignore the adapter update and wait for render to recreate.
    //

    if (m_pD3D && m_rgRenderers)
    {
        HMONITOR hMon = MonitorFromPoint(screenSpacePoint, MONITOR_DEFAULTTONULL);

        for (UINT i = 0; i < m_cAdapters; ++i)
        {
            if (hMon == m_pD3D->GetAdapterMonitor(i))
            {
                m_pCurrentRenderer = m_rgRenderers[i];
                break;
            }
        }
    }
}

D3DImage 컨테이너의 크기나 위치가 변경되면 모니터를 업데이트하거나 초당 몇 번 업데이트되는 DispatcherTimer를 사용하여 모니터를 업데이트합니다.

WPF 소프트웨어 렌더링

WPF는 다음과 같은 상황에서 소프트웨어의 UI 스레드에서 동기적으로 렌더링됩니다.

이러한 상황 중 하나가 발생하면 렌더링 시스템에서 하드웨어 버퍼를 소프트웨어에 복사하는 CopyBackBuffer 메서드를 호출합니다. 기본 구현은 표면을 사용하여 GetRenderTargetData 메서드를 호출합니다. 이 호출은 잠금/잠금 해제 패턴 외부에서 발생하므로 실패할 수 있습니다. 이 경우 CopyBackBuffer 메서드가 null을 반환하고 이미지가 표시되지 않습니다.

CopyBackBuffer 메서드를 재정의하고 기본 구현을 호출할 수 있으며 null을 반환하는 경우 자리 표시자 BitmapSource를 반환할 수 있습니다.

기본 구현을 호출하는 대신 사용자 고유의 소프트웨어 렌더링을 구현할 수도 있습니다.

참고

WPF가 소프트웨어에서 완전히 렌더링되는 경우 WPF에 프런트 버퍼가 없기 때문에 D3DImage가 표시되지 않습니다.

참고 항목