Udostępnij za pośrednictwem


Współdziałanie WPF i Direct3D9

Zawartość Direct3D9 można uwzględnić w aplikacji Windows Presentation Foundation (WPF). W tym temacie opisano sposób tworzenia zawartości Direct3D9 tak, aby efektywnie współdziałała z WPF.

Uwaga / Notatka

W przypadku korzystania z zawartości Direct3D9 w WPF należy również zastanowić się nad wydajnością. Aby uzyskać więcej informacji na temat optymalizowania pod kątem wydajności, zobacz Zagadnienia wydajności związane z technologią Direct3D9 i WPF.

Bufory wyświetlania

Klasa D3DImage zarządza dwoma buforami wyświetlania, które nazywają się buforem wstecznym i buforem przednim . Bufor wsteczny to powierzchnia Direct3D9. Zmiany w buforze wstecznym są kopiowane do buforu frontu podczas wywoływania metody Unlock.

Poniższa ilustracja przedstawia relację między buforem wstecznym a buforem frontu.

bufory wyświetlania D3DImage

Tworzenie urządzenia Direct3D9

Aby renderować zawartość Direct3D9, musisz utworzyć urządzenie Direct3D9. Istnieją dwa obiekty Direct3D9, których można użyć do utworzenia urządzenia, IDirect3D9 i IDirect3D9Ex. Użyj tych obiektów, aby utworzyć odpowiednio urządzenia IDirect3DDevice9 i IDirect3DDevice9Ex.

Utwórz urządzenie, wywołując jedną z następujących metod.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

W systemie operacyjnym Windows Vista lub nowszym użyj metody Direct3DCreate9Ex z wyświetlaczem skonfigurowanym do korzystania z modelu sterownika wyświetlania systemu Windows (WDDM). Użyj metody Direct3DCreate9 na dowolnej innej platformie.

Dostępność metody Direct3DCreate9Ex

d3d9.dll ma metodę Direct3DCreate9Ex tylko w systemie operacyjnym Windows Vista lub nowszym. Jeśli bezpośrednio połączysz funkcję w systemie Windows XP, nie można załadować aplikacji. Aby określić, czy metoda Direct3DCreate9Ex jest obsługiwana, załaduj bibliotekę DLL i poszukaj adresu proc. Poniższy kod pokazuje, jak przetestować metodę Direct3DCreate9Ex. Pełny przykład kodu można znaleźć w temacie Walkthrough: Creating Direct3D9 Content for Hosting in WPF( Tworzenie zawartości Direct3D9 na potrzeby hostingu wWPF).

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;
}

Tworzenie HWND

Tworzenie urządzenia wymaga HWND. Generalnie tworzy się fikcyjny HWND do użycia przez Direct3D9. W poniższym przykładzie kodu pokazano, jak utworzyć tymczasowy 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;
}

Obecne parametry

Utworzenie urządzenia wymaga również struktury D3DPRESENT_PARAMETERS, ale ważne jest tylko kilka parametrów. Te parametry są wybierane w celu zminimalizowania zużycia pamięci.

Ustaw pola BackBufferHeight i BackBufferWidth na 1. Ustawienie wartości 0 powoduje ustawienie ich na wymiary HWND.

Zawsze ustawiaj flagi D3DCREATE_MULTITHREADED i D3DCREATE_FPU_PRESERVE, aby zapobiec uszkodzeniu pamięci używanej przez direct3D9 i zapobiec zmianie ustawień fpu direct3D9.

Poniższy kod przedstawia sposób inicjowania struktury 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;
}

Tworzenie buforu wstecznego jako celu renderowania

Aby wyświetlić zawartość Direct3D9 w D3DImage, należy utworzyć powierzchnię Direct3D9 i przypisać ją, wywołując metodę SetBackBuffer.

Weryfikowanie obsługi adaptera

Przed utworzeniem powierzchni sprawdź, czy wszystkie adaptery obsługują wymagane właściwości powierzchni. Nawet jeśli renderujesz obraz na tylko jeden adapter, okno WPF może być wyświetlane na dowolnym adapterze w systemie. Zawsze należy pisać kod Direct3D9, który obsługuje konfiguracje wieloadapterowe, i należy sprawdzić wszystkie adaptery pod kątem obsługi, ponieważ WPF może przenieść powierzchnię między dostępnymi adapterami.

Poniższy przykład kodu pokazuje, jak sprawdzić wszystkie adaptery w systemie pod kątem obsługi 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;
}

Tworzenie powierzchni

Przed utworzeniem powierzchni sprawdź, czy możliwości urządzenia obsługują dobrą wydajność w docelowym systemie operacyjnym. Aby uzyskać więcej informacji, zobacz zagadnienia wydajności dotyczące współdziałania Direct3D9 i WPF.

Po zweryfikowaniu możliwości urządzenia można utworzyć powierzchnię. W poniższym przykładzie kodu pokazano, jak utworzyć obiekt docelowy renderowania.

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

W systemach operacyjnych Windows Vista i nowszych skonfigurowanych do używania narzędzia WDDM można utworzyć teksturę docelową renderowania i przekazać powierzchnię poziomu 0 do metody SetBackBuffer. To podejście nie jest zalecane w systemie Windows XP, ponieważ nie można utworzyć zablokowanej tekstury docelowej renderowania i wydajność zostanie zmniejszona.

Obsługa stanu urządzenia

Klasa D3DImage zarządza dwoma buforami wyświetlania, które nazywają się buforem wstecznym i buforem przednim . Bufor wsteczny to Twoja powierzchnia Direct3D. Zmiany w buforze wstecznym są kopiowane do buforu frontu podczas wywoływania metody Unlock, gdzie jest on wyświetlany na sprzęcie. Czasami bufor frontu staje się niedostępny. Ten brak dostępności może być spowodowany blokowaniem ekranu, ekskluzywnymi aplikacjami Direct3D pełnoekranowymi, przełączaniem użytkowników lub innymi działaniami systemowymi. W takim przypadku aplikacja WPF jest powiadamiana poprzez obsłużenie zdarzenia IsFrontBufferAvailableChanged. Sposób, w jaki aplikacja reaguje na to, że bufor przedni staje się niedostępny, zależy od tego, czy platforma WPF jest włączona, aby przełączyć się na renderowanie programowe. Metoda SetBackBuffer ma przeciążenie, które przyjmuje parametr określający, czy WPF powraca do renderowania oprogramowania.

Po wywołaniu przeciążenia SetBackBuffer(D3DResourceType, IntPtr) lub wywołaniu przeciążenia SetBackBuffer(D3DResourceType, IntPtr, Boolean) z parametrem enableSoftwareFallback ustawionym na false, system renderowania zwalnia odwołanie do buforu wstecznego, gdy bufor frontowy stanie się niedostępny i nic nie jest wyświetlane. Gdy przedni bufor jest ponownie dostępny, system renderowania generuje zdarzenie IsFrontBufferAvailableChanged, aby powiadomić aplikację WPF. Można utworzyć procedurę obsługi zdarzeń dla zdarzenia IsFrontBufferAvailableChanged, aby ponownie uruchomić renderowanie przy użyciu prawidłowej powierzchni Direct3D. Aby ponownie uruchomić renderowanie, należy wywołać metodę SetBackBuffer.

Po wywołaniu przeciążenia SetBackBuffer(D3DResourceType, IntPtr, Boolean) z parametrem enableSoftwareFallback ustawionym na truesystem renderowania zachowuje odwołanie do buforu wstecznego, gdy bufor frontu stanie się niedostępny, więc nie ma potrzeby wywoływania SetBackBuffer, gdy bufor frontu jest ponownie dostępny.

Gdy renderowanie oprogramowania jest włączone, mogą wystąpić sytuacje, w których urządzenie użytkownika staje się niedostępne, ale system renderowania zachowuje odwołanie do powierzchni Direct3D. Aby sprawdzić, czy urządzenie Direct3D9 jest niedostępne, wywołaj metodę TestCooperativeLevel. Aby sprawdzić urządzenia Direct3D9Ex, wywołaj metodę CheckDeviceState, ponieważ metoda TestCooperativeLevel jest przestarzała i zawsze zwraca powodzenie. Jeśli urządzenie użytkownika stanie się niedostępne, wywołaj SetBackBuffer, aby zwolnić referencję WPF do bufora zwrotnego. Jeśli musisz zresetować urządzenie, wywołaj SetBackBuffer przy użyciu parametru backBuffer ustawionego na null, a następnie ponownie wywołaj SetBackBuffer z backBuffer ustawionym na prawidłową powierzchnię Direct3D.

Wywołaj metodę Reset, aby odzyskać dane z nieprawidłowego urządzenia tylko wtedy, gdy implementujesz obsługę wielu kart. W przeciwnym razie zwolnij wszystkie interfejsy Direct3D9 i odtwórz je w całości na nowo. Jeśli układ karty uległ zmianie, obiekty Direct3D9 utworzone przed zmianą nie zostaną zaktualizowane.

Obsługa zmiany rozmiaru

Jeśli D3DImage jest wyświetlana w rozdzielczości innej niż jej rozmiar natywny, jest skalowana zgodnie z bieżącym BitmapScalingMode, z tą różnicą, że Bilinear jest zastępowana Fant.

Jeśli potrzebujesz większej wierności, musisz utworzyć nową powierzchnię, gdy kontener D3DImage zmieni rozmiar.

Istnieją trzy możliwe podejścia do obsługi zmiany rozmiaru.

  • Weź udział w systemie układu i utwórz nową powierzchnię po zmianie rozmiaru. Nie twórz zbyt wielu powierzchni, ponieważ pamięć wideo może być wyczerpana lub sfragmentowana.

  • Poczekaj, aż minie określony czas bez wystąpienia zdarzenia zmiany rozmiaru, aby utworzyć nową powierzchnię.

  • Utwórz DispatcherTimer, który sprawdza wymiary kontenera kilka razy na sekundę.

Optymalizacja z wieloma monitorami

Gdy system renderujący przenosi D3DImage do innego monitora, może to spowodować znaczne zmniejszenie wydajności.

W technologii WDDM, jeśli monitory są podłączone do tej samej karty wideo i używasz Direct3DCreate9Ex, to nie ma redukcji wydajności. Jeśli monitory znajdują się na oddzielnych kartach wideo, wydajność zostanie zmniejszona. W systemie Windows XP wydajność jest zawsze zmniejszana.

Gdy D3DImage przenosi się na inny monitor, możesz utworzyć nową powierzchnię na odpowiednim adapterze, aby przywrócić optymalną wydajność.

Aby uniknąć kary za wydajność, napisz kod przeznaczony specjalnie dla przypadku wielu monitorów. Poniższa lista przedstawia jeden ze sposobów pisania kodu z wieloma monitorami.

  1. Znajdź punkt D3DImage na ekranie za pomocą metody Visual.ProjectToScreen.

  2. Użyj metody MonitorFromPoint GDI, aby znaleźć monitor, który wyświetla punkt.

  3. Użyj metody IDirect3D9::GetAdapterMonitor, aby znaleźć adapter Direct3D9, do którego podłączony jest monitor.

  4. Jeśli karta nie jest taka sama jak karta z buforem wstecznym, utwórz nowy bufor wsteczny na nowym monitorze i przypisz go do bufora wstecznego D3DImage.

Uwaga / Notatka

Jeśli D3DImage znajduje się na granicy monitorów, wydajność będzie wolna, z wyjątkiem przypadków, gdy WDDM i IDirect3D9Ex są na tym samym adapterze. W tej sytuacji nie ma możliwości poprawy wydajności.

W poniższym przykładzie kodu pokazano, jak znaleźć bieżący monitor.

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;
            }
        }
    }
}

Zaktualizuj monitor po zmianie rozmiaru lub położenia kontenera D3DImage albo zaktualizuj monitor przy użyciu DispatcherTimer, który aktualizuje kilka razy na sekundę.

Renderowanie oprogramowania WPF

WPF jest renderowany synchronicznie w wątku interfejsu użytkownika w oprogramowaniu w następujących sytuacjach.

Gdy wystąpi jedna z tych sytuacji, system renderowania wywołuje metodę CopyBackBuffer, aby skopiować bufor sprzętowy do oprogramowania. Domyślna implementacja wywołuje metodę GetRenderTargetData dla twojej powierzchni. Ponieważ to wywołanie występuje poza wzorcem Blokada/Odblokowywanie, może zakończyć się niepowodzeniem. W tym przypadku metoda CopyBackBuffer zwraca null i nie jest wyświetlany żaden obraz.

Możesz zastąpić metodę CopyBackBuffer, wywołać implementację podstawową, a jeśli zwróci null, możesz zwrócić symbol zastępczy BitmapSource.

Możesz również zaimplementować własne renderowanie oprogramowania zamiast wywoływać implementację podstawową.

Uwaga / Notatka

Jeśli WPF jest renderowany całkowicie w trybie programowym, D3DImage nie jest wyświetlany, ponieważ WPF nie ma bufora przedniego.

Zobacz także