Partager via


Création de contenu Direct3D9 qui peut être hébergé dans WPF

Mise à jour : Juillet 2008

Vous pouvez inclure du contenu Direct3D9 dans une application Windows Presentation Foundation (WPF). Cette rubrique décrit comment créer du contenu Direct3D9 qui puisse interopérer efficacement avec WPF.

Remarque :

Lorsque vous utilisez du contenu Direct3D9 dans WPF, vous devez également tenir compte des performances. Pour plus d'informations sur l'optimisation des performances, consultez Considérations sur les performances de l'interopérabilité entre Direct3D9 et WPF.

Tampons d'affichage

La classe D3DImage gère deux mémoires tampons d'affichage, appelées mémoire tampon d'arrière-plan et tampon d'affichage. La mémoire tampon d'arrière-plan est votre surface Direct3D9. Les modifications apportées à la mémoire tampon d'arrière-plan sont copiées en avant dans le tampon d'affichage lorsque vous appelez la méthode Unlock.

L'illustration suivante montre la relation entre la mémoire tampon d'arrière-plan et le tampon d'affichage.

Tampons d'affichage D3DImage

Création d'un périphérique Direct3D9

Pour restituer du contenu Direct3D9, vous devez créer un périphérique Direct3D9. Vous pouvez utiliser deux objets Direct3D9 pour créer un périphérique : IDirect3D9 et IDirect3D9Ex. Utilisez ces objets pour créer respectivement des périphériques IDirect3DDevice9 et IDirect3DDevice9Ex.

Vous pouvez créer un périphérique en appelant l'une des méthodes suivantes :

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

Utilisez la méthode Direct3DCreate9Ex sur Windows Vista avec un affichage configuré pour utiliser le modèle du pilote d'affichage de Windows (WDDM). Utilisez la méthode Direct3DCreate9 sur toute autre plateforme.

Disponibilité de la méthode Direct3DCreate9Ex

Seul le fichier d3d9.dll sur Windows Vista dispose de la méthode Direct3DCreate9Ex. Si vous liez directement la fonction sur Windows XP, votre application risque de ne pas se charger. Pour déterminer si la méthode Direct3DCreate9Ex est prise en charge, chargez la DLL et recherchez l'adresse de procédure. Le code suivant indique comment tester la méthode Direct3DCreate9Ex. Pour obtenir l'exemple de code complet, consultez Procédure pas à pas : création de contenu Direct3D9 à héberger 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 périphérique passe par un HWND. En général, vous créez un HWND factice pour le Direct3D9. 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 Present

La création d'un périphérique requiert également une structure D3DPRESENT_PARAMETERS, mais seuls quelques paramètres sont importants. Ces paramètres sont choisis pour réduire l'encombrement de la mémoire.

Affectez aux champs BackBufferHeight et BackBufferWidth la valeur 1. Si vous leur affectez la valeur 0, les dimensions du HWND leur sont alors affectées.

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

Le code suivant indique comment initialiser la structure D3DPRESENT_PARAMETERS.

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 d'arrière-plan

Pour afficher du contenu Direct3D9 dans un D3DImage, vous pouvez créer et assigner une surface Direct3D9 en appelant la méthode SetBackBuffer.

Vérification de la prise en charge des adaptateurs

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 restituez sur un seul adaptateur, la fenêtre WPF peut s'afficher sur tout adaptateur du système. Vous devez toujours écrire du code Direct3D9 qui gère des configurations multi-adaptateur et vérifier la prise en charge de tous les adaptateurs, car WPF peut déplacer la surface d'un adaptateur à un autre en fonction de leur disponibilité.

L'exemple de code suivant indique comment vérifier la prise en charge de Direct3D9 par tous les adaptateurs du système.

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 fonctions du périphérique assurent de bonnes performance sur le système d'exploitation cible. Pour plus d'informations, consultez Considérations sur les performances de l'interopérabilité entre Direct3D9 et WPF.

Une fois les fonctions du périphérique vérifiées, vous pouvez créer la surface. L'exemple de code suivant indique 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

Si Windows Vista est configuré pour utiliser le WDDM, vous pouvez créer une texture de cible de rendu et passer la surface de niveau 0 à la méthode SetBackBuffer. Cette méthode n'est pas recommandée sur Windows XP, car vous ne pouvez pas créer de texture de cible de rendu verrouillable et les performances sont limitées.

Gestion de l'état du périphérique

Lorsque la valeur des transitions de propriété IsFrontBufferAvailable passe de true à false, WPF n'affiche pas le D3DImage et ne copie pas la mémoire tampon d'arrière-plan dans le tampon d'affichage. En règle générale, cela signifie que le périphérique est perdu.

Lorsque le périphérique est perdu, votre code doit arrêter le rendu et attendre que la valeur de la propriété IsFrontBufferAvailable passe à true. Gérez l'événement IsFrontBufferAvailableChanged de telle sorte qu'il soit notifié de cette transition.

Lorsque la valeur des transitions de propriété IsFrontBufferAvailable passe de false à true, vous devez vérifier le périphérique pour déterminer s'il est valide.

Pour les périphériques Direct3D9, appelez la méthode TestCooperativeLevel. Pour les périphériques Direct3D9Ex, appelez la méthode CheckDeviceState, car la méthode TestCooperativeLevel est désapprouvée et retourne toujours une valeur.

Si le périphérique est valide, appelez de nouveau la méthode SetBackBuffer avec la surface d'origine.

Si le périphérique n'est pas valide, vous devez réinitialiser le périphérique et recréer les ressources. Lorsque la méthode SetBackBuffer est appelée avec une surface à partir d'un périphérique non valide, une exception est levée.

Appelez la méthode Reset pour récupérer à partir d'un périphérique non valide uniquement si vous implémentez la prise en charge multi-adaptateur. Sinon, libérez toutes les interfaces Direct3D9 et recréez-les complètement. Si la disposition des adaptateurs a changé, les objets Direct3D9 créés avant le changement ne sont pas mis à jour.

Gestion du redimensionnement

En cas d'affichage d'un D3DImage avec une résolution différente de celle de sa taille d'origine, il est mis à l'échelle en fonction du BitmapScalingMode actuel, mais Bilinear est remplacé par Fant.

Si vous avez besoin d'une meilleure fidélité, vous devez créer une surface lorsque le conteneur du D3DImage change de taille.

Il existe trois méthodes de gestion du redimensionnement.

  • Participez au système de disposition et créez une surface lorsque la taille change. Ne créez pas trop de surfaces, sinon vous risquez de saturer ou de fragmenter la mémoire vidéo.

  • Attendez qu'un événement de redimensionnement ne se produise pas pendant une période déterminée pour créer la surface.

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

Optimisation multi-moniteur

Lorsque le système de rendu déplace un D3DImage vers un autre moniteur, cela risque d'altérer considérablement les performances.

Sur WDDM, à partir du moment où les moniteurs sont connectés à la même carte vidéo et que vous utilisez Direct3DCreate9Ex, les performances ne sont pas altérées. Si les moniteurs sont connectés à des cartes vidéo séparées, les performances s'en trouvent altérées. Sur Windows XP, les performances sont toujours altérées.

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

Pour éviter l'altération des performances, écrivez du code spécifiquement pour le cas multi-moniteur. La procédure ci-dessous indique une façon d'écrire du code multi-moniteur :

  1. Recherchez un point du D3DImage dans l'espace à l'écran avec la méthode Visual.ProjectToScreen.

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

  3. Utilisez la méthode IDirect3D9::GetAdapterMonitor pour rechercher l'adaptateur Direct3D9 auquel le moniteur est connecté.

  4. Si l'adaptateur n'est pas le même que l'adaptateur avec la mémoire tampon d'arrière-plan, créez une mémoire tampon d'arrière-plan sur le nouveau moniteur et assignez-la à la mémoire tampon d'arrière-plan D3DImage.

Remarque :

Si le D3DImage chevauche des moniteurs, les performances s'en trouvent altérées, sauf si WDDM et IDirect3D9Ex se trouvent sur le même adaptateur. Il est impossible d'améliorer les performances dans ce cas.

L'exemple de code suivant indique 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 position ou la taille du conteneur D3DImage change ou mettez à jour le moniteur à l'aide d'un DispatcherTimer qui s'actualise plusieurs fois par seconde.

Rendu logiciel WPF

WPF restitue de façon synchrone le thread d'interface utilisateur dans le logiciel dans les cas suivants :

Lorsque l'un de ces cas se produit, le système de rendu appelle la méthode CopyBackBuffer pour copier la mémoire tampon matérielle dans le logiciel. L'implémentation par défaut appelle la méthode GetRenderTargetData avec la surface. Comme cet appel se produit en dehors du modèle Verrouillage/Déverrouillage, il risque d'échouer. Dans ce cas, la méthode CopyBackBuffer 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 effectue une restitution complète dans le logiciel, D3DImage n'est pas indiqué, car WPF ne possède pas de tampon d'affichage.

Voir aussi

Tâches

Procédure pas à pas : création de contenu Direct3D9 à héberger dans WPF

Procédure pas à pas : hébergement de contenu Direct3D9 dans WPF

Concepts

Considérations sur les performances de l'interopérabilité entre Direct3D9 et WPF

Référence

D3DImage

Historique des modifications

Date

Historique

Raison

Juillet 2008

Ajout d'une nouvelle rubrique.

Modifications de fonctionnalités dans le SP1.