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.

D3DImage display buffers

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 sur n’importe Direct3DCreate9 quelle autre plateforme.

Disponibilité de la méthode Direct3DCreate9Ex

D3d9.dll possède 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 Direct3DCreate9Ex méthode est prise en charge, chargez la DLL et recherchez l’adresse de 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 appareil 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 BackBufferHeight champs sur BackBufferWidth 1. Le fait de les définir sur 0 les amène à définir les dimensions du HWND.

Définissez toujours les indicateurs et D3DCREATE_FPU_PRESERVE les D3DCREATE_MULTITHREADED indicateurs pour empêcher l’endommagement de la mémoire utilisée par Direct3D9 et empêcher Direct3D9 de modifier les paramètres du processeur 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 de la mémoire tampon back-tampon

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 case activée tous les adaptateurs à prendre en charge, car WPF peut déplacer la surface entre les adaptateurs disponibles.

L’exemple de code suivant montre comment case activée toutes les cartes 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 l’aire

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 l’interopérabilité Direct3D9 et WPF.

Lorsque vous avez vérifié les fonctionnalités de l’appareil, vous pouvez créer l’aire. 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 est 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 qui spécifie si WPF revient 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 enableSoftwareFallback paramètre défini truesur , le système de rendu conserve sa référence à la mémoire tampon arrière lorsque la mémoire tampon frontale devient indisponible, de sorte qu’il n’est pas nécessaire d’appeler SetBackBuffer lorsque la mémoire tampon frontale est à 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 case activée si un appareil Direct3D9 n’est pas disponible, appelez la TestCooperativeLevel méthode. Pour case activée les appareils Direct3D9Ex appellent la CheckDeviceState méthode, car la TestCooperativeLevel méthode est déconseillée et retourne toujours la réussite. Si l’appareil utilisateur n’est plus disponible, appelez SetBackBuffer la référence wpF à la mémoire tampon back. Si vous devez réinitialiser votre appareil, appelez SetBackBuffer avec le backBuffer paramètre défini nullsur , puis appelez SetBackBuffer à nouveau 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 résolution est affichée à une autre taille que sa taille native, elle est mise à l’échelle en fonction du courant BitmapScalingMode, sauf qu’elle Bilinear est remplacée par Fant.

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

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

  • Participez au système de disposition et créez une 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 case activée 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 utilisezDirect3DCreate9Ex, il n’y a aucune réduction des performances. Si les moniteurs se trouvent sur des carte vidéo distinctes, les performances sont réduites. Sur Windows XP, les performances sont toujours réduites.

Lorsque le D3DImage déplacement vers un autre moniteur, vous pouvez créer une surface sur 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 d’écrire du code à plusieurs moniteurs.

  1. Recherchez un point de l’espace d’écran D3DImage dans l’espace d’écran avec la Visual.ProjectToScreen méthode.

  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 moniteurs de straddles, les performances seront lentes, 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 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, appeler l’implémentation CopyBackBuffer de base et, si elle est retournée 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