Share via


Interaktion zwischen WPF und Direct3D9

Sie können Direct3D9-Inhalte in eine Windows Presentation Foundation (WPF)-Anwendung einschließen. In diesem Thema wird beschrieben, wie Sie Direct3D9-Inhalte erstellen, sodass sie effizient mit WPF interagieren.

Hinweis

Wenn Sie Direct3D9-Inhalte in WPF verwenden, müssen Sie auch über die Leistung nachdenken. Weitere Informationen zum Optimieren der Leistung finden Sie in den Leistungsüberlegungen zur Direct3D9 und WPF-Interoperabilität.

Anzeigen von Puffern

Die D3DImage-Klasse verwaltet zwei Anzeigepuffer, die als Hintergrundpuffer und Frontpuffer bezeichnet werden. Der Hintergrundpuffer ist Ihre Direct3D9-Oberfläche. Kopien der Änderungen am Hintergrundpuffer werden beim Aufrufen der Unlock-Methode an den Frontpuffer weitergeleitet.

Die folgende Abbildung zeigt die Beziehung zwischen dem Hintergrundpuffer und dem Frontpuffer.

D3DImage display buffers

Erstellung eines Direct3D9-Gerätes

Zum Rendern von Direct3D9-Inhalten müssen Sie ein Direct3D9-Gerät erstellen. Es gibt zwei Direct3D9-Objekte, die Sie zum Erstellen eines Geräts verwenden können, 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 unter dem Betriebssystem Windows Vista oder höher die Direct3DCreate9Ex-Methode mit einem Display, das für die Verwendung des Windows Display Driver Model (WDDM) konfiguriert ist. Auf jeder anderen Plattform verwenden Sie die Direct3DCreate9-Methode.

Verfügbarkeit der Direct3DCreate9Ex-Methode

Die Datei d3d9.dll verfügt nur unter Windows Vista oder einem neueren Betriebssystem über die Direct3DCreate9Ex-Methode. Wenn Sie die Funktion unter Windows XP direkt verknüpfen, kann die Anwendung nicht geladen werden. Um zu ermitteln, ob die Direct3DCreate9Ex-Methode unterstützt wird, laden Sie die DLL, und suchen Sie nach der Prozess-Adresse. Der folgende Code zeigt, wie die Direct3DCreate9Ex-Methode getestet werden kann. Ein vollständiges Codebeispiel finden Sie unter Exemplarische Vorgehensweise: Erstellen von Direct3D9-Inhalten für das Hosting 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

Das Erstellen eines Geräts erfordert einen HWND. Im Allgemeinen erstellen Sie einen Dummy-HWND, den Direct3D9 verwenden soll. 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;
}

Präsentieren von Parametern

Das Erstellen eines Geräts erfordert auch eine D3DPRESENT_PARAMETERS-Struktur, aber nur wenige Parameter sind wichtig. Diese Parameter werden ausgewählt, um den Speicherbedarf zu minimieren.

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

Legen Sie immer die KennzeichnungenD3DCREATE_MULTITHREADED und D3DCREATE_FPU_PRESERVE fest, um zu verhindern, dass der von Direct3D9 verwendete Arbeitsspeicher beschädigt wird, und um zu verhindern, dass Direct3D9 die FPU-Einstellungen ändert.

Der folgende Code zeigt, 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-Rendering-Ziels

Zum Anzeigen von Direct3D9-Inhalten in einem D3DImage erstellen Sie eine Direct3D9-Oberfläche und weisen sie durch Aufrufen der SetBackBuffer-Methode zu.

Überprüfen der Adapterunterstützung

Stellen Sie vor dem Erstellen einer Oberfläche sicher, dass alle Adapter die benötigten Oberflächeneigenschaften unterstützen. Auch wenn Sie nur einen Adapter rendern, wird das WPF-Fenster möglicherweise auf jedem Adapter im System angezeigt. Sie sollten immer Direct3D9-Code schreiben, der Konfigurationen mit mehreren Adaptern handhaben kann, und Sie sollten alle Adapter auf Unterstützung überprüfen, da WPF die Oberfläche möglicherweise zwischen den verfügbaren Adaptern verschieben kann.

Im folgenden Codebeispiel wird gezeigt, wie Sie alle Adapter im System auf die Direct3D9-Unterstützung überprüfen.

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 des Oberfläche

Prüfen Sie vor dem Erstellen einer Oberfläche, ob die Gerätefunktionen eine gute Leistung im Zielbetriebssystem unterstützen. Weitere Informationen 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 das Erstellen des Rendering-Ziels veranschaulicht.

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 und neueren Betriebssystemen, die für die Verwendung des WDDM konfiguriert sind, können Sie eine Rendering-Zieltextur erstellen und die Oberfläche der Ebene 0 an die SetBackBuffer-Methode übergeben. Dieser Ansatz wird für Windows XP nicht empfohlen, da Sie keine sperrbare Rendering-Zieltextur erstellen können und die Leistung verringert wird.

Handhabung des Gerätestatus

Die D3DImage-Klasse verwaltet zwei Anzeigepuffer, die als Hintergrundpuffer und Frontpuffer bezeichnet werden. Der Hintergrundpuffer ist Ihre Direct3D-Oberfläche. Kopien der Änderungen am Hintergrundpuffer werden beim Aufrufen der Unlock-Methode an den Frontpuffer weitergeleitet, wo sie auf der Hardware angezeigt werden. Gelegentlich ist der Frontpuffer nicht verfügbar. Dieser Mangel an Verfügbarkeit kann durch eine Bildschirmsperre, exklusive Direct3D-Anwendungen, Benutzerwechsel oder andere Systemaktivitäten verursacht werden. Wenn dies geschieht, wird Ihre WPF-Anwendung durch die Bearbeitung des IsFrontBufferAvailableChanged-Ereignisses benachrichtigt. Wie Ihre Anwendung reagiert, wenn der vordere Puffer nicht mehr verfügbar ist, hängt davon ab, ob WPF dafür aktiviert ist, auf Software-Rendering zurückzugreifen. Die SetBackBuffer-Methode verfügt über eine Überladung, die einen Parameter verwendet, der angibt, ob WPF auf das Softwarerendering zurückgreift.

Wenn Sie die SetBackBuffer(D3DResourceType, IntPtr)-Überladung oder die SetBackBuffer(D3DResourceType, IntPtr, Boolean)-Überladung mit einem auf false gesetzten enableSoftwareFallback-Parameter aufrufen, gibt das Rendering-System seinen Verweis auf den Hintergrundpuffer frei, wenn der Frontpuffer nicht mehr verfügbar ist und nichts angezeigt wird. Wenn der Frontpuffer wieder verfügbar ist, löst das Rendering-System das IsFrontBufferAvailableChanged-Ereignis aus, um Ihre WPF-Anwendung zu benachrichtigen. Sie können einen Ereignishandler für das IsFrontBufferAvailableChanged-Ereignis erstellen, um das Rendering mit einer gültigen Direct3D-Oberfläche neu zu starten. Um das Rendering neu zu starten, müssen Sie SetBackBuffer aufrufen.

Wenn Sie die SetBackBuffer(D3DResourceType, IntPtr, Boolean)-Überladung mit einem auf true gesetzten enableSoftwareFallback-Parameter aufrufen, behält das Rendering-System den Verweis auf den Hintergrundpuffer bei, wenn der Frontpuffer nicht mehr verfügbar ist. Daher muss SetBackBuffer nicht aufgerufen werden, wenn der Frontpuffer wieder verfügbar ist.

Wenn das Software-Rendering aktiviert ist, kann es Situationen geben, in denen das Gerät des Benutzers nicht mehr verfügbar ist, aber das Rendering-System einen Verweis auf die Direct3D-Oberfläche beibehält. Rufen Sie die TestCooperativeLevel-Methode auf, um zu überprüfen, ob ein Direct3D9-Gerät nicht verfügbar ist. Um ein Direct3D9Ex-Gerät zu überprüfen, rufen Sie die CheckDeviceState-Methode auf, da die TestCooperativeLevel-Methode veraltet ist und immer einen Erfolg zurückgibt. Wenn das Gerät eines Benutzers nicht mehr verfügbar ist, rufen Sie SetBackBuffer auf, um den Verweis von WPF auf den Hintergrundpuffer freizugeben. Wenn Sie Ihr Gerät zurücksetzen müssen, rufen Sie SetBackBuffer mit einem auf null festgelegten backBuffer-Parameter auf, und rufen Sie dann erneut SetBackBuffer auf, mit einem auf eine gültige Direct3D-Oberfläche festgelegten backBuffer.

Rufen Sie die Reset-Methode auf, um von einem ungültigen Gerät nur dann wiederherzustellen, wenn Sie die Unterstützung für mehrere Adapter implementieren. Lassen Sie andernfalls alle Direct3D9-Schnittstellen frei, und erstellen Sie sie vollständig neu. Wenn sich das Adapterlayout geändert hat, werden Direct3D9-Objekte, die vor der Änderung erstellt wurden, nicht aktualisiert.

Handhabung der Größenänderung

Wird ein D3DImage mit einer anderen Auflösung als seiner nativen Größe angezeigt, wird es entsprechend dem aktuellen BitmapScalingMode skaliert, mit der Ausnahme, dass Bilinear durch Fant ersetzt wird.

Wenn Sie eine höhere Originaltreue benötigen, müssen Sie eine neue Oberfläche erstellen, wenn der Container des D3DImage seine Größe ändert.

Es gibt drei mögliche Ansätze zum Handhabung der Größenänderung.

  • 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 den Videospeicher ausschöpfen oder fragmentieren.

  • Warten Sie, bis für eine bestimmte Zeitspanne kein Größenänderungsereignis aufgetreten ist, um die neue Oberfläche zu erstellen.

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

Optimierung für mehrere Monitore

Erhebliche Leistungseinbußen können entstehen, wenn das Rendering-System ein D3DImage auf einen anderen Monitor verschiebt.

Solange die Monitore an dieselbe Grafikkarte angeschlossen sind und Sie Direct3DCreate9Ex verwenden, gibt es bei WDDM keine Leistungseinbußen. Wenn sich die Monitore auf separaten Grafikkarten befinden, wird die Leistung reduziert. Bei Windows XP wird die Leistung immer reduziert.

Wenn das D3DImage auf einen anderen Monitor verschoben wird, können Sie eine neue Oberfläche auf dem entsprechenden Adapter erstellen, um eine gute Leistung wiederherzustellen.

Um die Leistungsnachteile zu vermeiden, schreiben Sie Code speziell für den Fall mit mehreren Monitoren. Die folgende Liste zeigt eine Möglichkeit, Code für mehrere Monitore zu schreiben.

  1. Suchen Sie mit der Visual.ProjectToScreen-Methode einen Punkt des D3DImage im Bildschirmbereich.

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

  3. Verwenden Sie die IDirect3D9::GetAdapterMonitor-Methode, um zu ermitteln, auf welchem Direct3D9-Adapter der Monitor aktiviert ist.

  4. Wenn der Adapter nicht mit dem Adapter mit dem Hintergrundpuffer identisch ist, erstellen Sie einen neuen Hintergrundpuffer für den neuen Monitor, und weisen Sie ihn dem Hintergrundpuffer des D3DImage zu.

Hinweis

Wenn sich das D3DImage über mehrere Monitore erstreckt, ist die Leistung langsam, außer im Fall von WDDM und mit IDirect3D9Ex auf demselben Adapter. In dieser Situation gibt es keine Möglichkeit, die Leistung zu verbessern.

Das folgende Codebeispiel zeigt, wie man den aktuellen Monitor findet.

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 Monitor, wenn sich die Größe oder Position des Containers des D3DImage ändert, oder aktualisieren Sie den Monitor mithilfe eines DispatcherTimer, der ein paar Mal pro Sekunde aktualisiert.

WPF-Software-Rendering

In den folgenden Situationen rendert WPF synchron im Thread der Benutzeroberfläche in der Software.

Wenn eine dieser Situationen auftritt, ruft das Rendering-System die CopyBackBuffer-Methode auf, um den Hardwarepuffer in die Software zu kopieren. Die Standardimplementierung ruft die GetRenderTargetData-Methode mit der Oberfläche auf. Da dieser Aufruf außerhalb des Sperr-/Entsperrmusters 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, wenn diese null zurückgibt, einen Platzhalter BitmapSource zurückgeben.

Sie können auch ihr eigenes Software-Rendering implementieren, anstatt die Basisimplementierung aufzurufen.

Hinweis

Wenn WPF vollständig in Software gerendert wird, wird D3DImage nicht angezeigt, da WPF keinen Frontpuffer hat.

Weitere Informationen