Share via


Interaktion zwischen WPF und Direct3D9

Sie können Direct3D9-Inhalt in eine WPF-Anwendung (Windows Presentation Foundation) einschließen. In diesem Thema wird beschrieben, wie Direct3D9-Inhalt so erstellt wird, dass er effizient mit WPF zusammenwirkt.

HinweisHinweis

Wenn Sie Direct3D9-Inhalt in WPF verwenden, müssen Sie auch über die Leistung nachdenken.Weitere Informationen zur Leistungsoptimierung finden Sie unter Überlegungen zur Leistung für die Interoperabilität zwischen Direct3D9 und WPF.

Anzeigen von Puffern

Die D3DImage-Klasse verwaltet zwei Anzeigepuffer, die als Hintergrundpuffer und Frontpuffer bezeichnet werden. Der Hintergrundpuffer ist die Direct3D-Oberfläche. Änderungen am Hintergrundpuffer werden beim Aufrufen der Unlock-Methode weiter in den Frontpuffer kopiert.

In der folgenden Abbildung wird die Beziehung zwischen dem Hintergrundpuffer und dem Frontpuffer dargestellt.

D3Dimage-Anzeigepuffer

Direct3D9-Geräteerstellung

Um Direct3D9-Inhalt zu rendern, müssen Sie ein Direct3D9-Gerät erstellen. Es gibt zwei Direct3D9-Objekte, die Sie verwenden können, um ein Gerät zu erstellen, IDirect3D9 und IDirect3D9Ex. Verwenden Sie diese Objekte, um IDirect3DDevice9- bzw. IDirect3DDevice9Ex-Geräte zu erstellen.

Erstellen Sie ein Gerät, indem Sie eine der folgenden Methoden aufrufen.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

Verwenden Sie die Direct3DCreate9Ex-Methode unter Windows Vista mit einer Anzeige, die für die Verwendung on WDDM (Windows Display Driver Model, Windows-Anzeigetreibermodell) konfiguriert ist. Verwenden Sie die Direct3DCreate9-Methode auf einer beliebigen anderen Plattform.

Verfügbarkeit der Direct3DCreate9Ex-Methode

Nur die Datei d3d9.dll unter Windows Vista bietet die Direct3DCreate9Ex-Methode. Wenn Sie die Funktion direkt unter Windows XP verknüpfen, wird die Anwendung nicht geladen. Um zu ermitteln, ob die Direct3DCreate9Ex-Methode unterstützt wird, laden Sie die DLL und suchen Sie nach der Prozeduradresse. Im folgenden Code wird dargestellt, wie für die Direct3DCreate9Ex-Methode getestet wird. Ein vollständiges Codebeispiel finden Sie unter Exemplarische Vorgehensweise: Erstellen von Direct3D9-Inhalten zum Hosten in 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;
}

HWND-Erstellung

Für die Erstellung eines Geräts ist ein HWND erforderlich. Im Allgemeinen erstellen Sie einen Dummy-HWND, der von Direct3D9 verwendet werden kann. Im folgenden Codebeispiel wird das Erstellen eines Dummy-HWND veranschaulicht.

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

Vorhandene Parameter

Für die Erstellung eines Geräts ist zwar auch eine D3DPRESENT_PARAMETERS-Struktur erforderlich, aber es sind nur einige Parameter wichtig. Diese Parameter werden ausgewählt, um die Speicherbeanspruchung zu minimieren.

Legen Sie die Felder BackBufferHeight und BackBufferWidth auf 1 fest. Wenn Sie diese Felder auf 0 festlegen, werden sie auf die Dimensionen des HWND festgelegt.

Legen Sie die Flags D3DCREATE_MULTITHREADED und D3DCREATE_FPU_PRESERVE fest, damit kein von Direct3D9 verwendeter Speicher beschädigt wird und damit Direct3D9 keine FPU-Einstellungen ändert.

Im folgenden Code wird dargestellt, wie die D3DPRESENT_PARAMETERS-Struktur initialisiert wird.

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

Erstellen des Hintergrundpuffer-Renderingziels

Wenn Sie Direct3D9-Inhalt in D3DImage anzeigen möchten, erstellen Sie eine Direct3D9-Oberfläche und weisen Sie diese zu, indem Sie die SetBackBuffer-Methode aufrufen.

Überprüfen der Adapterunterstützung

Bevor Sie eine Oberfläche erstellen, überprüfen Sie, dass alle Adapter die erforderlichen Oberflächeneigenschaften unterstützen. Selbst wenn Sie nur zu einem Adapter rendern, kann das WPF-Fenster für jeden beliebigen Adapter im System angezeigt werden. Sie sollten immer Direct3D9-Code schreiben, der Multiadapter-Konfigurationen verarbeitet, und sie sollten überprüfen, ob alle Adapter unterstützt werden, da WPF die Oberfläche zwischen den verfügbaren Adaptern verschieben kann.

Im folgenden Codebeispiel wird dargestellt, wie überprüft wird, ob alle Adapter auf dem System Direct3D9 unterstützen.

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

Erstellen der Oberfläche

Überprüfen Sie vor dem Erstellen einer Oberfläche, ob die Gerätefunktionen unter dem Zielbetriebssystem eine gute Leistung unterstützen. Weitere Informationen hierzu finden Sie unter Überlegungen zur Leistung für die Interoperabilität zwischen Direct3D9 und WPF.

Wenn Sie die Gerätefunktionen überprüft haben, können Sie die Oberfläche erstellen. Im folgenden Codebeispiel wird dargestellt, wie das Renderingziel erstellt wird.

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

Unter Windows Vista, das zur Verwendung des WDDM konfiguriert ist, können Sie eine Renderingzieltextur erstellen und die Oberfläche der Ebene 0 an die SetBackBuffer-Methode übergeben. Diese Methode wird unter Windows XP nicht empfohlen, da Sie keine sperrbare Zieltextur erstellen können und die Leistung reduziert wird.

Behandlung des Gerätezustands

Wenn die IsFrontBufferAvailable-Eigenschaft von true in false übergeht, zeigt WPF das D3DImage nicht an und der Hintergrundpuffer wird nicht in den Frontpuffer kopiert. Dies bedeutet normalerweise, dass das Gerät verloren gegangen ist.

Wenn das Gerät verloren ging, sollte Ihr Code das Rendering anhalten und darauf warten, dass die IsFrontBufferAvailable-Eigenschaft in true übergeht. Behandeln Sie das IsFrontBufferAvailableChanged-Ereignis so, dass es von diesem Übergang benachrichtigt wird.

Wenn die IsFrontBufferAvailable-Eigenschaft von false in true übergeht, sollten Sie Ihr Gerät prüfen, um zu ermitteln, ob es gültig ist.

Denn Direct3D9-Geräte rufen die TestCooperativeLevel-Methode auf. Denn Direct3D9Ex-Geräte rufen die CheckDeviceState-Methode auf, da die TestCooperativeLevel-Methode veraltet ist und immer erfolgreich zurückgegeben wird.

Wenn das Gerät gültig ist, rufen Sie erneut die SetBackBuffer-Methode mit der ursprünglichen Oberfläche auf.

Wenn das Gerät nicht gültig ist, müssen Sie das Gerät zurücksetzen und die Ressourcen erneut erstellen. Das Aufrufen der SetBackBuffer-Methode mit einer Oberfläche von einem ungültigen Gerät löst eine Ausnahme aus.

Rufen Sie die Reset-Methode für die Wiederherstellung von einem ungültigen Gerät nur dann auf, wenn Sie die Multiadapter-Unterstützung implementieren. Geben Sie andernfalls alle Direct3D9-Oberflächen frei und erstellen Sie diese völlig neu. Wenn sich das Adapterlayout geändert hat, werden die vor der Änderung erstellten Direct3D9-Objekte nicht aktualisiert.

Behandlung der Größenanpassung

Wenn D3DImage mit einer anderen Auflösung als in der systemeigenen Größe angezeigt wird, wird sie entsprechend dem aktuellen BitmapScalingMode skaliert, außer dass Bilinear durch Fant ersetzt wird.

Wenn Sie eine höhere Originaltreue benötigen, müssen Sie eine neue Oberfläche erstellen, sobald sich die Containergröße von D3DImage ändert.

Es gibt drei Methoden für die Größenanpassung.

  • Nehmen Sie am Layoutsystem teil und erstellen Sie eine neue Oberfläche, wenn sich die Größe ändert. Erstellen Sie nicht zu viele Oberflächen, da Sie möglicherweise Videospeicher erschöpfen oder fragmentieren.

  • Warten Sie, bis für einen längeren Zeitraum kein Größenanpassungsereignis stattgefunden hat, um die neue Oberfläche zu erstellen.

  • Erstellen Sie einen DispatcherTimer, der die Containerdimensionen mehrmals pro Sekunde überprüft.

Optimierung mit mehreren Bildschirmen

Die erheblich reduzierte Leistung kann sich ergeben, wenn das Renderingsystem ein D3DImage auf einen anderen Bildschirm verschiebt.

Solange sich die Bildschirme unter WDDM auf derselben Videokarte befinden und Sie Direct3DCreate9Ex verwenden, ist die Leistung nicht reduziert. Wenn sich die Bildschirme auf separaten Videokarten befinden, ist die Leistung reduziert. Unter Windows XP ist die Leistung immer reduziert.

Wenn das D3DImage auf einen anderen Bildschirm verschoben wird, können Sie auf dem entsprechenden Adapter eine neue Oberfläche für die Wiederherstellung einer guten Leistung erstellen.

Um Leistungseinbußen zu vermeiden, schreiben Sie Code ausdrücklich für den Fall von mehreren Bildschirmen. In der folgenden Liste wird eine Möglichkeit dargestellt, Code für mehrere Bildschirme zu schreiben.

  1. Suchen Sie mit der Visual.ProjectToScreen-Methode einen Punkt von D3DImage im Bildschirmkoordinatensystem.

  2. Verwenden Sie die MonitorFromPoint-GDI-Methode, um den Bildschirm zu suchen, der den Punkt anzeigt.

  3. Verwenden Sie die IDirect3D9::GetAdapterMonitor-Methode, um zu ermitteln, auf welchem Direct3D9-Adapter sich der Bildschirm befindet.

  4. Falls es sich bei dem Adapter nicht um den Adapter mit dem Hintergrundpuffer handelt, erstellen Sie für den neuen Bildschirm einen neuen Hintergrundpuffer und weisen Sie ihn dem D3DImage-Hintergrundpuffer zu.

HinweisHinweis

Falls D3DImage mehrere Monitore umspannt, führt dies zu einer langsamen Leistung, außer, wenn sich WDDM und IDirect3D9Ex auf demselben Adapter befinden.Es gibt keine Möglichkeit, die Leistung in dieser Situation zu verbessern.

Im folgenden Codebeispiel wird dargestellt, wie Sie den aktuellen Bildschirm suchen.

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

Aktualisieren Sie den Bildschirm, wenn sich die Containergröße oder Position von D3DImage ändert oder aktualisieren Sie den Bildschirm mit einem DispatcherTimer, der mehrere Aktualisierungen pro Sekunde durchführt.

WPF-Softwarerendering

WPF führt in den folgenden Situationen ein synchrones Rendering für den UI-Thread in Software durch.

Wenn eine der Situationen auftritt, ruft das Renderingsystem die CopyBackBuffer-Methode zum Kopieren des Hardwarepuffers in die Software auf. Die Standardimplementierung ruft die GetRenderTargetData-Methode mit der Oberfläche auf. Da dieser Aufruf außerhalb des Lock/Unlock-Musters (Sperren/Entsperren) auftritt, schlägt er möglicherweise fehl. In diesem Fall gibt die CopyBackBuffer-Methode null zurück, und es wird kein Bild angezeigt.

Sie können die CopyBackBuffer-Methode überschreiben, die Basisimplementierung aufrufen und falls null zurückgegeben wird, einen Platzhalter BitmapSource zurückgeben.

Sie können auch ein eigenes Softwarerendering implementieren, statt die Basisimplementierung aufzurufen.

HinweisHinweis

Falls WPF vollständig in der Software rendert, wird D3DImage nicht angezeigt, da WPF nicht über einen Frontpuffer verfügt.

Siehe auch

Aufgaben

Exemplarische Vorgehensweise: Erstellen von Direct3D9-Inhalten zum Hosten in WPF

Exemplarische Vorgehensweise: Hosten von Direct3D9-Inhalt in WPF

Referenz

D3DImage

Konzepte

Überlegungen zur Leistung für die Interoperabilität zwischen Direct3D9 und WPF