Dela via


WPF och Direct3D9 Interoperation

Du kan inkludera Direct3D9-innehåll i ett WPF-program (Windows Presentation Foundation). Det här avsnittet beskriver hur du skapar Direct3D9-innehåll så att det effektivt interoperates med WPF.

Obs

När du använder Direct3D9-innehåll i WPF måste du också tänka på prestanda. Mer information om hur du optimerar för prestanda finns i Prestandaöverväganden för Direct3D9 och WPF Interoperability.

Visa buffertar

Klassen D3DImage hanterar två visningsbuffertar, som kallas bakre buffert och främre buffert. Den bakre bufferten är din Direct3D9-yta. Ändringar i bufferten på baksidan kopieras framåt till den främre bufferten när du anropar metoden Unlock.

Följande bild visar relationen mellan den bakre bufferten och den främre bufferten.

D3DImage-visningsbuffertar

Skapa Direct3D9-enhet

Om du vill återge Direct3D9-innehåll måste du skapa en Direct3D9-enhet. Det finns två Direct3D9-objekt som du kan använda för att skapa en enhet, IDirect3D9 och IDirect3D9Ex. Använd dessa objekt för att skapa IDirect3DDevice9 respektive IDirect3DDevice9Ex enheter.

Skapa en enhet genom att anropa någon av följande metoder.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

I Windows Vista eller senare operativsystem använder du metoden Direct3DCreate9Ex med en skärm som är konfigurerad för att använda Windows Display Driver Model (WDDM). Använd metoden Direct3DCreate9 på någon annan plattform.

Tillgänglighet för Direct3DCreate9Ex-metoden

d3d9.dll har endast Direct3DCreate9Ex-metoden i Windows Vista eller senare operativsystem. Om du länkar funktionen direkt i Windows XP, misslyckas programmet att laddas. Om du vill ta reda på om metoden Direct3DCreate9Ex stöds läser du in DLL:en och letar efter proc-adressen. Följande kod visar hur du testar för metoden Direct3DCreate9Ex. Ett fullständigt kodexempel finns i Genomgång: Skapa Direct3D9-innehåll för hosting i 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;
}

Skapa HWND

För att skapa en enhet behövs en HWND. I allmänhet skapar du en dummy HWND för Direct3D9 att använda. Följande kodexempel visar hur du skapar en dummy 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;
}

Nuvarande parametrar

För att skapa en enhet krävs också en D3DPRESENT_PARAMETERS struct, men det är bara några få parametrar som är viktiga. Dessa parametrar väljs för att minimera minnesfotavtrycket.

Ange fälten BackBufferHeight och BackBufferWidth till 1. Om du ställer in dem till 0, sätts deras dimensioner till HWND:s dimensioner.

Ställ alltid in flaggorna D3DCREATE_MULTITHREADED och D3DCREATE_FPU_PRESERVE för att förhindra att minne som används av Direct3D9 skadas och för att förhindra att Direct3D9 ändrar FPU-inställningarna.

Följande kod visar hur du initierar D3DPRESENT_PARAMETERS struct.

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

Skapa renderingsmål för backbuffer

Om du vill visa Direct3D9-innehåll i en D3DImageskapar du en Direct3D9-yta och tilldelar den genom att anropa metoden SetBackBuffer.

Verifiera adapterstöd

Innan du skapar en yta, verifiera att alla adaptrar stöder de ytegenskaper du behöver. Även om du bara renderar till en enda adapter kan WPF-fönstret visas på valfri adapter i systemet. Du bör alltid skriva Direct3D9-kod som hanterar konfigurationer med flera kort, och du bör kontrollera om det finns stöd för alla kort, eftersom WPF kan flytta ytan mellan de tillgängliga korten.

Följande kodexempel visar hur du kontrollerar alla kort i systemet för Direct3D9-stöd.

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

Skapa ytan

Innan du skapar en yta kontrollerar du att enhetsfunktionerna stöder bra prestanda på måloperativsystemet. Mer information finns i prestandaöverväganden för Direct3D9 och WPF Interoperability.

När du har verifierat enhetsfunktioner kan du skapa ytan. I följande kodexempel visas hur du skapar återgivningsmålet.

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

I Windows Vista och senare operativsystem, som är konfigurerade för att använda WDDM, kan du skapa en renderingsmålstruktur och skicka nivå 0-ytan till metoden SetBackBuffer. Den här metoden rekommenderas inte i Windows XP eftersom du inte kan skapa en låsbar renderad målstruktur och prestandan kommer att minskas.

Hantera enhetstillstånd

Klassen D3DImage hanterar två visningsbuffertar, som kallas bakre buffert och främre buffert. Den bakre bufferten är din Direct3D-yta. Ändringar i den bakre bufferten kopieras framåt till den främre bufferten när du anropar metoden Unlock, där den visas på maskinvaran. Ibland blir den främre bufferten otillgänglig. Den här bristen på tillgänglighet kan orsakas av skärmlåsning, fullskärms exklusiva Direct3D-program, användarväxling eller andra systemaktiviteter. När detta inträffar meddelas ditt WPF-program genom att hantera händelsen IsFrontBufferAvailableChanged. Hur programmet svarar på att den främre bufferten blir otillgänglig beror på om WPF har aktiverats för att återgå till programvarurendering. Metoden SetBackBuffer har en överlagrad form som tar en parameter som anger om WPF ska återgå till programvarurendering.

När du anropar SetBackBuffer(D3DResourceType, IntPtr) överladdning eller SetBackBuffer(D3DResourceType, IntPtr, Boolean) överladdning med parametern enableSoftwareFallback satt till false, släpper återgivningssystemet sin referens till den bakre bufferten när den främre bufferten blir otillgänglig och inget visas. När den främre bufferten är tillgänglig igen genererar renderingssystemet den IsFrontBufferAvailableChanged händelsen för att meddela WPF-programmet. Du kan skapa en händelsehanterare för den IsFrontBufferAvailableChanged händelsen för att starta om återgivningen med en giltig Direct3D-yta. Om du vill starta om renderingen måste du anropa SetBackBuffer.

När du anropar SetBackBuffer(D3DResourceType, IntPtr, Boolean) överlagring med parametern enableSoftwareFallback inställd på truebehåller återgivningssystemet sin referens till den bakre bufferten när den främre bufferten blir otillgänglig, så det finns ingen anledning att anropa SetBackBuffer när den främre bufferten är tillgänglig igen.

När programvarurendering är aktiverat kan det finnas situationer där användarens enhet blir otillgänglig, men återgivningssystemet behåller en referens till Direct3D-ytan. Om du vill kontrollera om en Direct3D9-enhet inte är tillgänglig anropar du metoden TestCooperativeLevel. För att verifiera en Direct3D9Ex-enhet, använd metoden CheckDeviceState, eftersom metoden TestCooperativeLevel är inaktuell och alltid returnerar ett lyckat resultat. Om användarenheten har blivit otillgänglig, anropar du SetBackBuffer för att frigöra WPF:s referens till bakbufferten. Om du behöver återställa enheten anropar du SetBackBuffer med parametern backBuffer inställd på nulloch anropar sedan SetBackBuffer igen med backBuffer inställt på en giltig Direct3D-yta.

Anropa metoden Reset för att återställa en ogiltig enhet endast om du implementerar multiadapterstöd. Annars släpper du alla Direct3D9-gränssnitt och återskapar dem helt. Om adapterlayouten har ändrats, uppdateras inte Direct3D9-objekt som skapats innan ändringen.

Hantera storleksjustering

Om en D3DImage visas med en annan upplösning än den ursprungliga storleken skalas den enligt den aktuella BitmapScalingMode, förutom att Bilinear ersätts med Fant.

Om du behöver högre återgivning måste du skapa en ny yta när containern för D3DImage ändrar storlek.

Det finns tre möjliga metoder för att hantera storleksändring.

  • Delta i layoutsystemet och skapa en ny yta när storleken ändras. Skapa inte för många ytor eftersom du kan uttömma eller fragmenta videominnet.

  • Vänta tills en storleksändringshändelse inte har inträffat under en fast tidsperiod för att skapa den nya ytan.

  • Skapa en DispatcherTimer som kontrollerar containerdimensionerna flera gånger per sekund.

Optimering med flera bildskärmar

Avsevärt lägre prestanda kan uppstå när renderingssystemet flyttar en D3DImage till en annan bildskärm.

På WDDM, så länge bildskärmarna finns på samma grafikkort och du använder Direct3DCreate9Ex, finns det ingen minskning av prestanda. Om bildskärmarna finns på separata grafikkort minskar prestandan. I Windows XP minskar alltid prestandan.

När D3DImage flyttas till en annan bildskärm kan du skapa en ny yta på motsvarande adapter för att återställa bra prestanda.

För att undvika prestandastraffet skriver du kod specifikt för fallet med flera övervakare. I följande lista visas ett sätt att skriva kod för flera övervakare.

  1. Hitta en punkt i D3DImage i skärmutrymmet med metoden Visual.ProjectToScreen.

  2. Använd GDI-metoden MonitorFromPoint för att hitta bildskärmen som visar punkten.

  3. Använd metoden IDirect3D9::GetAdapterMonitor för att hitta vilken Direct3D9-adapter som skärmen är ansluten till.

  4. Om adaptern inte är densamma som adaptern med bakbufferten, skapar du en ny bakbuffert på den nya bildskärmen och tilldelar den bakbufferten med D3DImage.

Not

Om D3DImage spänner över monitorer blir prestandan långsam, förutom när det gäller WDDM och IDirect3D9Ex på samma adapter. Det finns inget sätt att förbättra prestanda i den här situationen.

Följande kodexempel visar hur du hittar den aktuella skärmen.

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

Uppdatera övervakaren när D3DImage containerns storlek eller position ändras, eller uppdatera övervakaren med hjälp av en DispatcherTimer som uppdateras några gånger per sekund.

WPF-programvarurendering

WPF renderas synkront på användargränssnittstråden i programvara i följande situationer.

När en av dessa situationer inträffar anropar renderingssystemet CopyBackBuffer-metoden för att kopiera maskinvarubufferten till programvara. Standardimplementeringen anropar GetRenderTargetData-metoden med din yta. Eftersom det här anropet inträffar utanför lås-/upplåsningsmönstret kan det misslyckas. I det här fallet returnerar metoden CopyBackBuffernull och ingen bild visas.

Du kan åsidosätta metoden CopyBackBuffer, anropa basimplementeringen och om den returnerar nullkan du returnera en platshållare BitmapSource.

Du kan också implementera din egen programvaruåtergivning i stället för att anropa basimplementeringen.

Obs

Om WPF återges helt i programvara visas inte D3DImage eftersom WPF inte har någon främre buffert.

Se även