Partager via


Interopérabilité WPF et Direct3D9

Vous pouvez inclure du contenu Direct3D9 dans une application WPF (Windows Presentation Foundation). Cette rubrique explique comment créer du contenu Direct3D9 afin qu’il interopére efficacement avec WPF.

Remarque

Lorsque vous utilisez du contenu Direct3D9 dans WPF, vous devez également réfléchir aux performances. Pour plus d’informations sur l’optimisation des performances, consultez Considérations relatives aux performances pour l’interopérabilité Direct3D9 et WPF.

Afficher les mémoires tampons

La D3DImage classe gère deux mémoires tampons d’affichage, appelées mémoire tampon principale et mémoire tampon frontale. La mémoire tampon arrière est votre surface Direct3D9. Les modifications apportées à la mémoire tampon arrière sont copiées vers la mémoire tampon frontale lorsque vous appelez la Unlock méthode.

L’illustration suivante montre la relation entre la mémoire tampon arrière et la mémoire tampon frontale.

Mémoires tampons d’affichage D3DImage

Création d’appareils Direct3D9

Pour afficher le contenu Direct3D9, vous devez créer un appareil Direct3D9. Il existe deux objets Direct3D9 que vous pouvez utiliser pour créer un appareil et IDirect3D9IDirect3D9Ex. Utilisez ces objets pour créer IDirect3DDevice9 et IDirect3DDevice9Ex appareils, respectivement.

Créez un appareil en appelant l’une des méthodes suivantes.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

Sur windows Vista ou un système d’exploitation ultérieur, utilisez la Direct3DCreate9Ex méthode avec un affichage configuré pour utiliser le modèle de pilote d’affichage Windows (WDDM). Utilisez la méthode Direct3DCreate9 sur n’importe quelle autre plateforme.

Disponibilité de la méthode Direct3DCreate9Ex

Le d3d9.dll a la Direct3DCreate9Ex méthode uniquement sur Windows Vista ou un système d’exploitation ultérieur. Si vous liez directement la fonction sur Windows XP, votre application ne parvient pas à se charger. Pour déterminer si la méthode Direct3DCreate9Ex est prise en charge, chargez la DLL et recherchez l’adresse de la procédure. Le code suivant montre comment tester la Direct3DCreate9Ex méthode. Pour obtenir un exemple de code complet, consultez procédure pas à pas : création de contenu Direct3D9 pour l’hébergement dans 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;
}

Création HWND

La création d’un dispositif nécessite un HWND. En général, vous créez un HWND factice pour Direct3D9 à utiliser. L’exemple de code suivant montre comment créer un HWND factice.

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

Paramètres présents

La création d’un appareil nécessite également un D3DPRESENT_PARAMETERS struct, mais seuls quelques paramètres sont importants. Ces paramètres sont choisis pour réduire l’empreinte mémoire.

Définissez les champs BackBufferHeight et BackBufferWidth à 1. Le fait de les régler sur 0 provoque qu'ils soient ajustés aux dimensions du HWND.

Définissez toujours les indicateurs D3DCREATE_MULTITHREADED et D3DCREATE_FPU_PRESERVE afin d'éviter d'endommager la mémoire utilisée par Direct3D9 et d'empêcher Direct3D9 de modifier les paramètres FPU.

Le code suivant montre comment initialiser le 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;
}

Création de la cible de rendu du tampon arrière

Pour afficher le contenu Direct3D9 dans un D3DImage, vous créez une surface Direct3D9 et l’affectez en appelant la SetBackBuffer méthode.

Vérification de la prise en charge de l’adaptateur

Avant de créer une surface, vérifiez que tous les adaptateurs prennent en charge les propriétés de surface dont vous avez besoin. Même si vous effectuez un rendu sur un seul adaptateur, la fenêtre WPF peut s’afficher sur n’importe quelle carte du système. Vous devez toujours écrire du code Direct3D9 qui gère les configurations multi-adaptateurs, et vous devez vérifier tous les adaptateurs pour la prise en charge, car WPF peut déplacer la surface parmi les adaptateurs disponibles.

L’exemple de code suivant montre comment vérifier tous les adaptateurs sur le système pour la prise en charge de 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;
}

Création de la Surface

Avant de créer une surface, vérifiez que les fonctionnalités de l’appareil prennent en charge de bonnes performances sur le système d’exploitation cible. Pour plus d’informations, consultez Considérations relatives aux performances pour Direct3D9 et WPF Interopérabilité.

** Lorsque vous avez vérifié les fonctionnalités de l’appareil, vous pouvez créer la surface. L’exemple de code suivant montre comment créer la cible de rendu.

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

Sur les systèmes d’exploitation Windows Vista et ultérieurs, qui sont configurés pour utiliser wdDM, vous pouvez créer une texture cible de rendu et passer la surface de niveau 0 à la SetBackBuffer méthode. Cette approche n’est pas recommandée sur Windows XP, car vous ne pouvez pas créer une texture cible de rendu verrouillée et les performances seront réduites.

Gestion de l’état de l’appareil

La D3DImage classe gère deux mémoires tampons d’affichage, appelées mémoire tampon principale et mémoire tampon frontale. La mémoire tampon arrière représente votre surface Direct3D. Les modifications apportées à la mémoire tampon arrière sont copiées vers la mémoire tampon frontale lorsque vous appelez la Unlock méthode, où elle est affichée sur le matériel. Parfois, la mémoire tampon frontale devient indisponible. Ce manque de disponibilité peut être dû au verrouillage de l’écran, aux applications Direct3D exclusives à l’écran, au changement d’utilisateur ou à d’autres activités système. Lorsque cela se produit, votre application WPF est avertie en gérant l’événement IsFrontBufferAvailableChanged . La façon dont votre application répond à la mémoire tampon frontale devenant indisponible dépend du fait que WPF est activé pour revenir au rendu logiciel. La SetBackBuffer méthode a une surcharge qui prend un paramètre spécifiant si WPF recourt au rendu logiciel.

Lorsque vous appelez la SetBackBuffer(D3DResourceType, IntPtr) surcharge ou appelez la SetBackBuffer(D3DResourceType, IntPtr, Boolean) surcharge avec le enableSoftwareFallback paramètre défini falsesur , le système de rendu libère sa référence à la mémoire tampon arrière lorsque la mémoire tampon frontale devient indisponible et que rien n’est affiché. Lorsque la mémoire tampon frontale est à nouveau disponible, le système de rendu déclenche l’événement IsFrontBufferAvailableChanged pour notifier votre application WPF. Vous pouvez créer un gestionnaire d’événements pour que l’événement IsFrontBufferAvailableChanged redémarre le rendu avec une surface Direct3D valide. Pour redémarrer le rendu, vous devez appeler SetBackBuffer.

Lorsque vous appelez la SetBackBuffer(D3DResourceType, IntPtr, Boolean) surcharge avec le paramètre enableSoftwareFallback défini sur true, le système de rendu conserve sa référence au tampon arrière lorsque le tampon avant devient indisponible, de sorte qu’il n’est pas nécessaire d’appeler SetBackBuffer lorsque le tampon avant est de nouveau disponible.

Lorsque le rendu logiciel est activé, il peut y avoir des situations où l’appareil de l’utilisateur devient indisponible, mais que le système de rendu conserve une référence à l’aire Direct3D. Pour vérifier si un appareil Direct3D9 n’est pas disponible, appelez la TestCooperativeLevel méthode. Pour vérifier le fonctionnement d'un appareil Direct3D9Ex, utilisez la méthode CheckDeviceState, car la méthode TestCooperativeLevel est déconseillée et renvoie toujours un succès. Si l’appareil utilisateur n’est plus disponible, appelez SetBackBuffer pour libérer la référence de WPF au tampon arrière. Si vous devez réinitialiser votre appareil, appelez SetBackBuffer avec le paramètre backBuffer défini sur null, puis appelez à nouveau SetBackBuffer avec backBuffer défini sur une surface Direct3D valide.

Appelez la Reset méthode pour récupérer à partir d’un appareil non valide uniquement si vous implémentez la prise en charge de plusieurs adaptateurs. Sinon, relâchez toutes les interfaces Direct3D9 et recréez-les complètement. Si la disposition de l’adaptateur a changé, les objets Direct3D9 créés avant la modification ne sont pas mis à jour.

Gestion du redimensionnement

Si une D3DImage est affichée à une résolution autre que sa taille native, elle est mise à l'échelle selon le BitmapScalingMode actuel, sauf que Bilinear est substitué par Fant.

Si vous avez besoin d’une fidélité plus élevée, vous devez créer une nouvelle surface lorsque la taille du conteneur de D3DImage change.

Il existe trois approches possibles pour gérer le redimensionnement.

  • Participez au système de mise en page et créez une nouvelle surface lorsque la taille change. Ne créez pas trop de surfaces, car vous risquez d’épuiser ou de fragmenter la mémoire vidéo.

  • Attendez qu’un événement de redimensionnement n’ait pas eu lieu pendant une période fixe pour créer la nouvelle surface.

  • Créez un DispatcherTimer qui vérifie les dimensions du conteneur plusieurs fois par seconde.

Optimisation multi-moniteur

Une réduction significative des performances peut se produire lorsque le système de rendu déplace un moniteur vers un D3DImage autre moniteur.

Sur WDDM, tant que les moniteurs se trouvent sur la même carte vidéo et que vous utilisez Direct3DCreate9Ex, il n’y a aucune réduction des performances. Si les moniteurs se trouvent sur des cartes vidéo distinctes, les performances sont réduites. Sur Windows XP, les performances sont toujours réduites.

Lorsque le D3DImage se déplace vers un autre moniteur, vous pouvez créer une nouvelle surface avec l'adaptateur correspondant pour restaurer de bonnes performances.

Pour éviter la pénalité de performances, écrivez du code spécifiquement pour le cas multi-moniteur. La liste suivante montre une façon de concevoir du code pour plusieurs moniteurs.

  1. Trouvez un point de l’élément D3DImage dans l’espace d’écran à l’aide de la méthode Visual.ProjectToScreen.

  2. Utilisez la MonitorFromPoint méthode GDI pour rechercher le moniteur qui affiche le point.

  3. Utilisez la IDirect3D9::GetAdapterMonitor méthode pour rechercher l’adaptateur Direct3D9 sur lequel se trouve le moniteur.

  4. Si l’adaptateur n’est pas identique à l’adaptateur avec la mémoire tampon de retour, créez une mémoire tampon de retour sur le nouveau moniteur et affectez-la à la D3DImage mémoire tampon de retour.

Remarque

Si les D3DImage chevauchent les moniteurs, les performances seront ralenties, sauf dans le cas de WDDM et IDirect3D9Ex sur le même adaptateur. Il n’existe aucun moyen d’améliorer les performances dans cette situation.

L’exemple de code suivant montre comment rechercher le moniteur actuel.

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

Mettez à jour le moniteur lorsque la D3DImage taille ou la position du conteneur change, ou mettez à jour le moniteur à l’aide d’une DispatcherTimer mise à jour quelques fois par seconde.

Rendu logiciel WPF

WPF s'affiche de façon synchrone sur le thread d'interface utilisateur dans le logiciel dans les situations suivantes.

Lorsque l’une de ces situations se produit, le système de rendu appelle la CopyBackBuffer méthode pour copier la mémoire tampon matérielle vers le logiciel. L’implémentation par défaut appelle la GetRenderTargetData méthode avec votre surface. Étant donné que cet appel se produit en dehors du modèle Lock/Unlock, il peut échouer. Dans ce cas, la CopyBackBuffer méthode retourne null et aucune image n’est affichée.

Vous pouvez remplacer la méthode CopyBackBuffer, appeler l’implémentation de base, et si elle retourne null, vous pouvez retourner un espace réservé BitmapSource.

Vous pouvez également implémenter votre propre rendu logiciel au lieu d’appeler l’implémentation de base.

Remarque

Si WPF est rendu complètement dans le logiciel, D3DImage n’est pas affiché, car WPF n’a pas de mémoire tampon frontale.

Voir aussi