Udostępnij za pośrednictwem


WPF i Direct3D9 — Współdziałanie

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

W przypadku korzystania z zawartości Direct3D9 w WPF należy również zastanowić się nad wydajnością. Aby uzyskać więcej informacji o sposobie optymalizacji pod kątem wydajności, zobacz Zagadnienia dotyczące wydajności w przypadku współdziałania Direct3D9 i WPF.

Bufory wyświetlania

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

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

D3DImage display buffers

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 i IDirect3D9IDirect3D9Ex. Użyj tych obiektów, aby utworzyć IDirect3DDevice9 odpowiednio urządzenia 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 Direct3DCreate9Ex metody z wyświetlaczem skonfigurowanym do korzystania z modelu sterownika wyświetlania systemu Windows (WDDM). Direct3DCreate9 Użyj metody na dowolnej innej platformie.

Dostępność metody Direct3DCreate9Ex

Biblioteka 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 jest obsługiwana Direct3DCreate9Ex , 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 Przewodnik: tworzenie zawartości Direct3D9 na potrzeby hostingu w WPF.

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. Ogólnie rzecz biorąc, można utworzyć fikcyjny HWND dla Direct3D9 do użycia. W poniższym przykładzie kodu pokazano, jak utworzyć fikcyjny kod 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 D3DPRESENT_PARAMETERS również struktury, ale ważne jest tylko kilka parametrów. Te parametry są wybierane w celu zminimalizowania zużycia pamięci.

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

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

Poniższy kod pokazuje, jak zainicjować D3DPRESENT_PARAMETERS strukturę.

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 obiektu docelowego renderowania buforu wstecznego

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

Weryfikowanie obsługi adaptera

Przed utworzeniem powierzchni sprawdź, czy wszystkie karty obsługują wymagane właściwości powierzchni. Nawet jeśli renderujesz tylko jedną kartę, okno WPF może być wyświetlane na dowolnej karcie w systemie. Zawsze należy napisać kod Direct3D9, który obsługuje konfiguracje wielu kart sieciowych i należy sprawdzić wszystkie karty pod kątem obsługi, ponieważ WPF może przenieść powierzchnię między dostępnymi kartami.

Poniższy przykład kodu pokazuje, jak sprawdzić wszystkie karty 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 dotyczące 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, które są skonfigurowane do używania WDDM, można utworzyć teksturę docelową renderowania i przekazać powierzchnię poziomu 0 do SetBackBuffer metody . 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 zarządza dwoma D3DImage buforami wyświetlania, które są nazywane buforem wstecznym i buforem frontu. Bufor wsteczny to powierzchnia Direct3D. Zmiany w buforze wstecznym są kopiowane do buforu frontu podczas wywoływania Unlock metody , gdzie jest ona wyświetlana 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 przez obsługę IsFrontBufferAvailableChanged zdarzenia. Sposób, w jaki aplikacja reaguje na bufor frontu staje się niedostępny, zależy od tego, czy platforma WPF jest włączona, aby powrócić do renderowania oprogramowania. Metoda SetBackBuffer ma przeciążenie, które przyjmuje parametr określający, czy WPF powraca do renderowania oprogramowania.

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

Po wywołaniu SetBackBuffer(D3DResourceType, IntPtr, Boolean) przeciążenia z parametrem enableSoftwareFallback ustawionym na true, system renderowania zachowuje odwołanie do buforu wstecznego, gdy bufor frontu staje 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ć, czy urządzenia Direct3D9Ex wywołają metodę CheckDeviceState , ponieważ TestCooperativeLevel metoda jest przestarzała i zawsze zwraca powodzenie. Jeśli urządzenie użytkownika stanie się niedostępne, wywołaj metodę SetBackBuffer zwolnienia odwołania WPF do buforu wstecznego. Jeśli musisz zresetować urządzenie, wywołaj SetBackBuffer metodę z parametrem ustawionym backBuffer na null, a następnie wywołaj SetBackBuffer ponownie element z backBuffer ustawioną 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 utwórz je całkowicie. Jeśli układ karty uległ zmianie, obiekty Direct3D9 utworzone przed zmianą nie zostaną zaktualizowane.

Obsługa zmiany rozmiaru

Jeśli element D3DImage jest wyświetlany w rozdzielczości innej niż rozmiar macierzysty, jest skalowany zgodnie z bieżącym BitmapScalingModeparametrem , z tą różnicą, że Bilinear jest zastępowany wartością Fant.

Jeśli potrzebujesz większej wierności, musisz utworzyć nową powierzchnię, gdy kontener zmienia D3DImage 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 fragment.

  • Poczekaj, aż zdarzenie zmiany rozmiaru nie zostanie utworzone przez określony czas, aby utworzyć nową powierzchnię.

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

Optymalizacja z wieloma monitorami

Znacznie zmniejszona wydajność może spowodować przeniesienie systemu D3DImage renderowania do innego monitora.

W usłudze WDDM, o ile monitory znajdują się na tej samej karcie wideo i używasz Direct3DCreate9Exmetody , nie ma żadnej redukcji wydajności. Jeśli monitory znajdują się na oddzielnych kartach wideo, wydajność zostanie zmniejszona. W systemie Windows XP wydajność jest zawsze zmniejszana.

Po przejściu D3DImage do innego monitora można utworzyć nową powierzchnię na odpowiedniej karcie, aby przywrócić dobrą 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 w obszarze ekranu za pomocą Visual.ProjectToScreen metody .

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

  3. Użyj metody , aby znaleźć kartę IDirect3D9::GetAdapterMonitor Direct3D9 monitora.

  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 buforu wstecznego D3DImage .

Uwaga

D3DImage Jeśli straddles monitoruje, wydajność będzie niska, z wyjątkiem WDDM i IDirect3D9Ex na tej samej karcie. 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 D3DImage zmianie rozmiaru lub położenia kontenera albo zaktualizuj monitor przy użyciu tego monitora DispatcherTimer 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 z powierzchnią. Ponieważ to wywołanie występuje poza wzorcem Blokada/Odblokowywanie, może zakończyć się niepowodzeniem. W takim przypadku CopyBackBuffer metoda zwraca wartość null i nie jest wyświetlany żaden obraz.

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

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

Uwaga

Jeśli WPF jest renderowanie całkowicie w oprogramowaniu, nie jest wyświetlany, D3DImage ponieważ WPF nie ma buforu frontu.

Zobacz też