Bagikan melalui


Interoperasi WPF dan Direct3D9

Anda dapat menyertakan konten Direct3D9 dalam aplikasi Windows Presentation Foundation (WPF). Topik ini menjelaskan cara membuat konten Direct3D9 sehingga secara efisien menginteroperaksi dengan WPF.

Catatan

Saat menggunakan konten Direct3D9 di WPF, Anda juga perlu memikirkan performa. Untuk informasi selengkapnya tentang cara mengoptimalkan performa, lihat Pertimbangan Performa untuk Interoperabilitas Direct3D9 dan WPF.

Tampilkan Buffer

Kelas ini D3DImage mengelola dua buffer tampilan, yang disebut buffer belakang dan buffer depan. Buffer belakang adalah permukaan Direct3D9 Anda. Perubahan pada buffer belakang disalin ke buffer depan saat Anda memanggil Unlock metode .

Ilustrasi berikut menunjukkan hubungan antara buffer belakang dan buffer depan.

D3DImage display buffers

Pembuatan Perangkat Direct3D9

Untuk merender konten Direct3D9, Anda harus membuat perangkat Direct3D9. Ada dua objek Direct3D9 yang dapat Anda gunakan untuk membuat perangkat, IDirect3D9 dan IDirect3D9Ex. Gunakan objek ini untuk membuat IDirect3DDevice9 dan IDirect3DDevice9Ex perangkat, masing-masing.

Buat perangkat dengan memanggil salah satu metode berikut.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

Pada Windows Vista atau sistem operasi yang lebih baru, gunakan Direct3DCreate9Ex metode dengan tampilan yang dikonfigurasi untuk menggunakan Windows Display Driver Model (WDDM). Gunakan metode pada Direct3DCreate9 platform lain.

Ketersediaan metode Direct3DCreate9Ex

d3d9.dll hanya memiliki Direct3DCreate9Ex metode pada Windows Vista atau sistem operasi yang lebih baru. Jika Anda langsung menautkan fungsi di Windows XP, aplikasi Anda gagal dimuat. Untuk menentukan apakah Direct3DCreate9Ex metode didukung, muat DLL dan cari alamat proc. Kode berikut menunjukkan cara menguji Direct3DCreate9Ex metode . Untuk contoh kode lengkap, lihat Panduan: Membuat Konten Direct3D9 untuk Hosting di 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;
}

Pembuatan HWND

Membuat perangkat memerlukan HWND. Secara umum, Anda membuat HWND dummy untuk digunakan Direct3D9. Contoh kode berikut menunjukkan cara membuat HWND dummy.

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

Parameter Sajikan

Membuat perangkat juga memerlukan D3DPRESENT_PARAMETERS struct, tetapi hanya beberapa parameter yang penting. Parameter ini dipilih untuk meminimalkan jejak memori.

Atur BackBufferHeight bidang dan BackBufferWidth ke 1. Mengaturnya ke 0 menyebabkannya diatur ke dimensi HWND.

Selalu atur D3DCREATE_MULTITHREADED bendera dan D3DCREATE_FPU_PRESERVE untuk mencegah kerusakan memori yang digunakan oleh Direct3D9 dan untuk mencegah Direct3D9 mengubah pengaturan FPU.

Kode berikut menunjukkan cara menginisialisasi D3DPRESENT_PARAMETERS struktur.

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

Membuat Target Penyajian Buffer Belakang

Untuk menampilkan konten Direct3D9 dalam D3DImage, Anda membuat permukaan Direct3D9 dan menetapkannya dengan memanggil SetBackBuffer metode .

Memverifikasi Dukungan Adapter

Sebelum membuat permukaan, verifikasi bahwa semua adaptor mendukung properti permukaan yang Anda butuhkan. Bahkan jika Anda hanya merender ke satu adaptor, jendela WPF dapat ditampilkan pada adaptor apa pun dalam sistem. Anda harus selalu menulis kode Direct3D9 yang menangani konfigurasi multi-adaptor, dan Anda harus memeriksa semua adaptor untuk mendapatkan dukungan, karena WPF mungkin memindahkan permukaan di antara adaptor yang tersedia.

Contoh kode berikut menunjukkan cara memeriksa semua adaptor pada sistem untuk dukungan 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;
}

Membuat Surface

Sebelum membuat permukaan, verifikasi bahwa kemampuan perangkat mendukung performa yang baik pada sistem operasi target. Untuk informasi selengkapnya, lihat Pertimbangan Performa untuk Interoperabilitas Direct3D9 dan WPF.

Ketika Anda memiliki kemampuan perangkat terverifikasi, Anda dapat membuat permukaan. Contoh kode berikut menunjukkan cara membuat target render.

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

Pada Windows Vista dan sistem operasi yang lebih baru, yang dikonfigurasi untuk menggunakan WDDM, Anda dapat membuat tekstur target render dan meneruskan permukaan tingkat 0 ke SetBackBuffer metode . Pendekatan ini tidak disarankan pada Windows XP, karena Anda tidak dapat membuat tekstur target render yang dapat dikunci dan performa akan berkurang.

Menangani Status Perangkat

Kelas ini D3DImage mengelola dua buffer tampilan, yang disebut buffer belakang dan buffer depan. Buffer belakang adalah permukaan Direct3D Anda. Perubahan pada buffer belakang disalin ke buffer depan saat Anda memanggil Unlock metode , tempat buffer ditampilkan pada perangkat keras. Terkadang, buffer depan menjadi tidak tersedia. Kurangnya ketersediaan ini dapat disebabkan oleh penguncian layar, aplikasi Direct3D eksklusif layar penuh, pengalihan pengguna, atau aktivitas sistem lainnya. Ketika ini terjadi, aplikasi WPF Anda akan diberi tahu dengan menangani peristiwa tersebut IsFrontBufferAvailableChanged . Bagaimana aplikasi Anda merespons buffer depan menjadi tidak tersedia tergantung pada apakah WPF diaktifkan untuk kembali ke penyajian perangkat lunak. Metode SetBackBuffer ini memiliki kelebihan beban yang mengambil parameter yang menentukan apakah WPF kembali ke penyajian perangkat lunak.

Ketika Anda memanggil SetBackBuffer(D3DResourceType, IntPtr) kelebihan beban atau memanggil SetBackBuffer(D3DResourceType, IntPtr, Boolean) kelebihan beban dengan parameter yang enableSoftwareFallback diatur ke false, sistem penyajian merilis referensinya ke buffer belakang ketika buffer depan menjadi tidak tersedia dan tidak ada yang ditampilkan. Ketika buffer depan tersedia lagi, sistem penyajian meningkatkan IsFrontBufferAvailableChanged peristiwa untuk memberi tahu aplikasi WPF Anda. Anda dapat membuat penanganan aktivitas untuk peristiwa untuk IsFrontBufferAvailableChanged memulai ulang penyajian lagi dengan permukaan Direct3D yang valid. Untuk memulai ulang penyajian, Anda harus memanggil SetBackBuffer.

Ketika Anda memanggil SetBackBuffer(D3DResourceType, IntPtr, Boolean) kelebihan beban dengan enableSoftwareFallback parameter yang diatur ke true, sistem penyajian mempertahankan referensinya ke buffer belakang ketika buffer depan menjadi tidak tersedia, jadi tidak perlu memanggil SetBackBuffer ketika buffer depan tersedia lagi.

Ketika penyajian perangkat lunak diaktifkan, mungkin ada situasi di mana perangkat pengguna menjadi tidak tersedia, tetapi sistem penyajian mempertahankan referensi ke permukaan Direct3D. Untuk memeriksa apakah perangkat Direct3D9 tidak tersedia, panggil TestCooperativeLevel metode . Untuk memeriksa perangkat Direct3D9Ex, panggil CheckDeviceState metode , karena TestCooperativeLevel metode tidak digunakan lagi dan selalu mengembalikan keberhasilan. Jika perangkat pengguna menjadi tidak tersedia, panggil SetBackBuffer untuk merilis referensi WPF ke buffer belakang. Jika Anda perlu mengatur ulang perangkat, panggil SetBackBuffer dengan parameter yang backBuffer diatur ke null, lalu panggil SetBackBuffer lagi dengan backBuffer diatur ke permukaan Direct3D yang valid.

Reset Panggil metode untuk memulihkan dari perangkat yang tidak valid hanya jika Anda menerapkan dukungan multi-adaptor. Jika tidak, rilis semua antarmuka Direct3D9 dan buat ulang sepenuhnya. Jika tata letak adaptor telah berubah, objek Direct3D9 yang dibuat sebelum perubahan tidak diperbarui.

Menangani Pengubahan Ukuran

D3DImage Jika ditampilkan pada resolusi selain ukuran aslinya, itu diskalakan sesuai dengan saat ini BitmapScalingMode, kecuali yang Bilinear diganti untuk Fant.

Jika Anda memerlukan keakuratan yang lebih tinggi, Anda harus membuat permukaan baru saat kontainer D3DImage ukuran perubahan.

Ada tiga kemungkinan pendekatan untuk menangani pengubahan ukuran.

  • Berpartisipasi dalam sistem tata letak dan buat permukaan baru saat ukuran berubah. Jangan membuat terlalu banyak permukaan, karena Anda mungkin kelelahan atau fragmen memori video.

  • Tunggu hingga peristiwa pengubahan ukuran belum terjadi selama periode waktu tetap untuk membuat permukaan baru.

  • DispatcherTimer Buat yang memeriksa dimensi kontainer beberapa kali per detik.

Pengoptimalan multi-monitor

Performa yang berkurang secara signifikan dapat mengakibatkan ketika sistem penyajian memindahkan D3DImage ke monitor lain.

Di WDDM, selama monitor berada di kartu video yang sama dan Anda menggunakan Direct3DCreate9Ex, tidak ada pengurangan performa. Jika monitor berada di kartu video terpisah, performa berkurang. Pada Windows XP, performa selalu berkurang.

D3DImage Saat berpindah ke monitor lain, Anda dapat membuat permukaan baru pada adaptor yang sesuai untuk memulihkan performa yang baik.

Untuk menghindari penalti performa, tulis kode khusus untuk kasus multi-monitor. Daftar berikut menunjukkan salah satu cara untuk menulis kode multi-monitor.

  1. Temukan titik ruang D3DImage layar dengan Visual.ProjectToScreen metode .

  2. MonitorFromPoint Gunakan metode GDI untuk menemukan monitor yang menampilkan titik .

  3. IDirect3D9::GetAdapterMonitor Gunakan metode untuk menemukan adaptor Direct3D9 mana yang berada di monitor.

  4. Jika adaptor tidak sama dengan adaptor dengan buffer belakang, buat buffer belakang baru pada monitor baru dan tetapkan ke D3DImage buffer belakang.

Catatan

D3DImage Jika straddles memantau, performa akan lambat, kecuali dalam kasus WDDM dan IDirect3D9Ex pada adaptor yang sama. Tidak ada cara untuk meningkatkan performa dalam situasi ini.

Contoh kode berikut menunjukkan cara menemukan monitor saat ini.

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

Perbarui monitor saat D3DImage ukuran atau posisi kontainer berubah, atau perbarui monitor dengan menggunakan DispatcherTimer yang diperbarui beberapa kali per detik.

Penyajian Perangkat Lunak WPF

WPF merender secara sinkron pada utas UI dalam perangkat lunak dalam situasi berikut.

Ketika salah satu situasi ini terjadi, sistem penyajian memanggil CopyBackBuffer metode untuk menyalin buffer perangkat keras ke perangkat lunak. Implementasi default memanggil GetRenderTargetData metode dengan permukaan Anda. Karena panggilan ini terjadi di luar pola Kunci/Buka Kunci, panggilan mungkin gagal. Dalam hal ini, CopyBackBuffer metode mengembalikan null dan tidak ada gambar yang ditampilkan.

Anda dapat mengambil CopyBackBuffer alih metode , memanggil implementasi dasar, dan jika mengembalikan null, Anda dapat mengembalikan tempat penampung BitmapSource.

Anda juga dapat menerapkan penyajian perangkat lunak Anda sendiri alih-alih memanggil implementasi dasar.

Catatan

Jika WPF merender sepenuhnya dalam perangkat lunak, D3DImage tidak ditampilkan karena WPF tidak memiliki buffer depan.

Baca juga