Sdílet prostřednictvím


Vzájemná spolupráce grafického subsystému WPF a systému Direct3D9

Obsah Direct3D9 můžete zahrnout do aplikace WPF (Windows Presentation Foundation). Toto téma popisuje, jak vytvořit obsah Direct3D9 tak, aby efektivně interoperoval s WPF.

Poznámka:

Při používání obsahu Direct3D9 ve WPF je také potřeba přemýšlet o výkonu. Další informace o tom, jak optimalizovat výkon, najdete v tématu Důležité informace o výkonu pro interoperabilitu Direct3D9 a WPF.

Zobrazení vyrovnávacích pamětí

Třída D3DImage spravuje dvě vyrovnávací paměti zobrazení, které se nazývají zadní vyrovnávací paměť a přední vyrovnávací paměť. Zadní vyrovnávací paměť je plocha Direct3D9. Při volání Unlock metody se změny vyrovnávací paměti zpět zkopírují do frontové vyrovnávací paměti.

Následující obrázek znázorňuje vztah mezi zadní vyrovnávací pamětí a frontovou vyrovnávací pamětí.

D3DImage display buffers

Vytvoření zařízení Direct3D9

Pokud chcete vykreslit obsah Direct3D9, musíte vytvořit zařízení Direct3D9. Existují dva objekty Direct3D9, které můžete použít k vytvoření zařízení a IDirect3D9IDirect3D9Ex. Tyto objekty použijte k vytvoření IDirect3DDevice9 a IDirect3DDevice9Ex zařízení.

Vytvořte zařízení voláním jedné z následujících metod.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

V operačním systému Windows Vista nebo novější použijte Direct3DCreate9Ex metodu s displejem, který je nakonfigurován pro použití modelu ovladače zobrazení systému Windows (WDDM). Použijte metodu Direct3DCreate9 na jakékoli jiné platformě.

Dostupnost metody Direct3DCreate9Ex

D3d9.dll má metodu Direct3DCreate9Ex pouze v operačním systému Windows Vista nebo novější. Pokud funkci přímo propojíte v systému Windows XP, aplikace se nenačte. Chcete-li zjistit, zda Direct3DCreate9Ex je metoda podporována, načtěte knihovnu DLL a vyhledejte adresu proc. Následující kód ukazuje, jak otestovat metodu Direct3DCreate9Ex . Úplný příklad kódu najdete v tématu Návod: Vytvoření obsahu Direct3D9 pro hostování ve 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;
}

Vytvoření HWND

Vytvoření zařízení vyžaduje HWND. Obecně platí, že vytvoříte fiktivní HWND pro Direct3D9, který se má použít. Následující příklad kódu ukazuje, jak vytvořit fiktivní 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;
}

Prezentovat parametry

Vytvoření zařízení také vyžaduje D3DPRESENT_PARAMETERS strukturu, ale důležité jsou jenom některé parametry. Tyto parametry jsou zvoleny tak, aby se minimalizovaly nároky na paměť.

BackBufferHeight Nastavte pole a BackBufferWidth pole na 1. Nastavení na hodnotu 0 způsobí, že budou nastaveny na rozměry HWND.

Vždy nastavte a D3DCREATE_FPU_PRESERVE označte příznak, D3DCREATE_MULTITHREADED aby se zabránilo poškození paměti používané rozhraním Direct3D9 a aby se zabránilo změně nastavení FPU 3D9.

Následující kód ukazuje, jak inicializovat D3DPRESENT_PARAMETERS strukturu.

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

Vytvoření cíle vykreslení vyrovnávací paměti zpět

Pokud chcete zobrazit obsah Direct3D9 v objektu D3DImage, vytvoříte plochu Direct3D9 a přiřadíte ho voláním SetBackBuffer metody.

Ověření podpory adaptéru

Před vytvořením povrchu ověřte, že všechny adaptéry podporují požadované vlastnosti povrchu. I když vykreslujete pouze jeden adaptér, může se okno WPF zobrazit na jakémkoli adaptéru v systému. Vždy byste měli napsat kód Direct3D9, který zpracovává konfigurace více adaptérů, a měli byste zkontrolovat všechny adaptéry pro podporu, protože WPF může přesouvat povrch mezi dostupnými adaptéry.

Následující příklad kódu ukazuje, jak zkontrolovat všechny adaptéry v systému pro podporu 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;
}

Vytvoření zařízení Surface

Před vytvořením povrchu ověřte, že možnosti zařízení podporují dobrý výkon cílového operačního systému. Další informace najdete v tématu Důležité informace o výkonu pro interoperabilitu Direct3D9 a WPF.

Až budete mít ověřené možnosti zařízení, můžete vytvořit povrch. Následující příklad kódu ukazuje, jak vytvořit cíl vykreslení.

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

Ve Windows Vista a novějších operačních systémech, které jsou nakonfigurovány pro použití WDDM, můžete vytvořit cílovou texturu vykreslení a předat povrch úrovně 0 metodě SetBackBuffer . Tento přístup se nedoporučuje v systému Windows XP, protože nelze vytvořit zamknutelnou cílovou texturu vykreslení a výkon bude snížen.

Zpracování stavu zařízení

Třída D3DImage spravuje dvě vyrovnávací paměti zobrazení, které se nazývají zadní vyrovnávací paměť a přední vyrovnávací paměť. Zadní vyrovnávací paměť je plocha Direct3D. Změny vyrovnávací paměti zpět se zkopírují vpřed do front vyrovnávací paměti při volání Unlock metody, kde se zobrazí na hardwaru. Občas se front buffer stane nedostupnou. Příčinou tohoto nedostatku dostupnosti může být uzamčení obrazovky, exkluzivní aplikace Direct3D na celé obrazovce, přepínání uživatelů nebo jiné systémové aktivity. Pokud k tomu dojde, aplikace WPF bude upozorněna zpracováním IsFrontBufferAvailableChanged události. Způsob, jakým vaše aplikace reaguje na nedostupnou frontovou vyrovnávací paměť, závisí na tom, jestli je povoleno vrátit se do softwarového vykreslování. Metoda SetBackBuffer má přetížení, které přebírá parametr, který určuje, zda WPF spadá zpět do softwarového vykreslování.

Při volání SetBackBuffer(D3DResourceType, IntPtr) přetížení nebo volání SetBackBuffer(D3DResourceType, IntPtr, Boolean) přetížení s enableSoftwareFallback parametrem nastaveným na false, vykreslovací systém uvolní svůj odkaz na zadní vyrovnávací paměť, když front vyrovnávací paměť přestane být k dispozici a nic se nezobrazí. Jakmile bude front buffer opět k dispozici, vyvolá vykreslovací systém IsFrontBufferAvailableChanged událost, která oznámí aplikaci WPF. Pro událost můžete vytvořit obslužnou rutinu IsFrontBufferAvailableChanged události, která znovu restartuje vykreslování s platnou plochou Direct3D. Chcete-li restartovat vykreslování, musíte volat SetBackBuffer.

Když zavoláte SetBackBuffer(D3DResourceType, IntPtr, Boolean) přetížení s parametrem enableSoftwareFallback nastaveným na true, systém vykreslování si zachová svůj odkaz na zadní vyrovnávací paměť, když front vyrovnávací paměť přestane být k dispozici, takže není nutné volat SetBackBuffer , když front buffer je opět k dispozici.

Pokud je povolené vykreslování softwaru, může dojít k situacím, kdy bude zařízení uživatele nedostupné, ale systém vykreslování zachová odkaz na plochu Direct3D. Pokud chcete zkontrolovat, jestli zařízení Direct3D9 není k dispozici, zavolejte metodu TestCooperativeLevel . Chcete-li zkontrolovat zařízení Direct3D9Ex volání CheckDeviceState metody, protože TestCooperativeLevel metoda je zastaralá a vždy vrací úspěch. Pokud se uživatelské zařízení stane nedostupným, zavolejte odkaz SetBackBuffer WPF na zadní vyrovnávací paměť. Pokud potřebujete resetovat zařízení, zavolejte SetBackBuffer parametrem backBuffer nastaveným na nulla pak znovu volejte SetBackBuffer s nastaveným backBuffer na platnou plochu Direct3D.

Volejte metodu Reset , která se má obnovit z neplatného zařízení, pouze pokud implementujete podporu více adaptérů. Jinak uvolněte všechna rozhraní Direct3D9 a znovu je vytvořte úplně. Pokud se změnilo rozložení adaptéru, objekty Direct3D9 vytvořené před změnou se neaktualizují.

Zpracování změny velikosti

D3DImage Pokud je zobrazen v jiném rozlišení než jeho nativní velikost, je škálován podle aktuálního BitmapScalingMode, s výjimkou, že Bilinear je nahrazen .Fant

Pokud požadujete vyšší věrnost, musíte při změně velikosti kontejneru D3DImage vytvořit nový povrch.

Existují tři možné přístupy ke zpracování změny velikosti.

  • Zapojte se do systému rozložení a při změně velikosti vytvořte novou plochu. Nevytvádřujte příliš mnoho povrchů, protože můžete vyčerpat nebo fragmentovat paměť videa.

  • Počkejte, až se událost změny velikosti nenasadí na pevnou dobu, aby se vytvořila nová plocha.

  • Vytvořte kontejnerovou DispatcherTimer dimenzi několikrát za sekundu.

Optimalizace s více monitory

Výrazně nižší výkon může vést k tomu, že vykreslovací systém přesune D3DImage do jiného monitoru.

Na WDDM, pokud jsou monitory na stejné grafické kartě a používáte Direct3DCreate9Ex, neexistuje žádné snížení výkonu. Pokud jsou monitory na samostatných grafických kartách, sníží se výkon. V systému Windows XP je výkon vždy snížen.

D3DImage Když se přesune na jiný monitor, můžete vytvořit nový povrch na odpovídajícím adaptéru, aby se obnovil dobrý výkon.

Abyste se vyhnuli snížení výkonu, napište kód speciálně pro případ s více monitory. Následující seznam ukazuje jeden ze způsobů, jak napsat kód s více monitory.

  1. Najděte bod D3DImage v prostoru na obrazovce pomocí Visual.ProjectToScreen metody.

  2. MonitorFromPoint Pomocí metody GDI vyhledejte monitor, který zobrazuje bod.

  3. IDirect3D9::GetAdapterMonitor Pomocí metody zjistěte, na kterém adaptéru Direct3D9 je monitor zapnutý.

  4. Pokud adaptér není stejný jako adaptér se záložní vyrovnávací pamětí, vytvořte novou vyrovnávací paměť zpět na novém monitoru D3DImage a přiřaďte ho zpět vyrovnávací paměti.

Poznámka:

D3DImage Pokud straddles monitoruje, výkon bude pomalý, s výjimkou případu WDDM a IDirect3D9Ex na stejném adaptéru. V této situaci neexistuje žádný způsob, jak zlepšit výkon.

Následující příklad kódu ukazuje, jak najít aktuální monitorování.

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

Aktualizujte monitor, když D3DImage se změní velikost nebo umístění kontejneru, nebo monitor aktualizujte pomocí DispatcherTimer aktualizace několikrát za sekundu.

Vykreslování softwaru WPF

WPF se synchronně vykresluje ve vlákně uživatelského rozhraní v softwaru v následujících situacích.

Pokud dojde k některé z těchto situací, systém vykreslování volá metodu CopyBackBuffer ke zkopírování hardwarové vyrovnávací paměti do softwaru. Výchozí implementace volá metodu GetRenderTargetData s vaším povrchem. Vzhledem k tomu, že k tomuto volání dochází mimo vzor Lock/Unlock, může selhat. V tomto případě CopyBackBuffer metoda vrátí null a nezobrazí se žádný obrázek.

Můžete přepsat metodu CopyBackBuffer , volat základní implementaci a pokud se vrátí null, můžete vrátit zástupný symbol BitmapSource.

Můžete také implementovat vlastní vykreslování softwaru místo volání základní implementace.

Poznámka:

Pokud WPF vykresluje zcela v softwaru, nezobrazí se, D3DImage protože WPF nemá front buffer.

Viz také