Поделиться через


Взаимодействие WPF и Direct3D9

Содержимое Direct3D9 можно включить в приложение Windows Presentation Foundation (WPF). В этом разделе описывается, как создать содержимое Direct3D9, которое будет эффективно взаимодействовать с WPF.

ПримечаниеПримечание

При использовании содержимого Direct3D9 в WPF также нужно думать о производительности.Дополнительные сведения об оптимизации производительности см. в разделе Вопросы производительности, связанные с взаимодействием Direct3D9 и WPF.

Буферы отображения

Класс D3DImage управляет двумя буферами отображения, которые называются задним буфером и передним буфером. Задний буфер — это поверхность Direct3D9. Изменения заднего буфера копируются в передний буфер при вызове метода Unlock.

На следующем рисунке показано отношение между задним и передним буфером.

Буферы отображения D3DImage

Создание устройства Direct3D9

Чтобы отобразить содержимое Direct3D9, необходимо создать устройство Direct3D9. Имеются два объекта Direct3D9, которые можно использовать для создания устройства — IDirect3D9 и IDirect3D9Ex. Используйте эти объекты для создания устройств IDirect3DDevice9 и IDirect3DDevice9Ex соответственно.

Создайте устройство вызовом одного из следующих методов.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

Используйте метод Direct3DCreate9Ex в Windows Vista с дисплеем, сконфигурированным для использования модели драйвера монитора Windows (WDDM). Используйте метод Direct3DCreate9 для любой другой платформы.

Доступность метода Direct3DCreate9Ex

Метод Direct3DCreate9Ex имеется только в библиотеке d3d9.dll в Windows Vista. При прямой ссылке на эту функцию в Windows XP приложение не удастся загрузить. Чтобы определить, поддерживается ли метод Direct3DCreate9Ex, загрузите DLL и найдите адрес процедуры. В следующем коде показано, как протестировать метод Direct3DCreate9Ex. Полный пример кода см. в разделе Пошаговое руководство. Создание содержимого Direct3D9 для размещения в 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

Для создания устройства требуется HWND. В общем случае, можно создать вспомогательный HWND для использования Direct3D9. В следующем примере кода показан способ создания вспомогательного объекта HWND.

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

Представление параметров

Для создания устройства также требуется структура D3DPRESENT_PARAMETERS, но важны только несколько параметров. Эти параметры выбраны для минимизации объема памяти.

Полям BackBufferHeight и BackBufferWidth присвойте значение 1. При значении 0 потребуется задать размеры HWND.

Всегда устанавливайте флаги D3DCREATE_MULTITHREADED и D3DCREATE_FPU_PRESERVE, чтобы предупредить повреждение памяти, используемой Direct3D9, и защитить Direct3D9 от изменения параметров FPU.

В следующем коде показано, как инициализировать структуру 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;
}

Создание заднего целевого буфера визуализации

Чтобы отобразить содержимое Direct3D9 в D3DImage, создайте поверхность Direct3D9 и назначьте ее вызовом метода SetBackBuffer.

Проверка поддержки адаптера

Перед созданием поверхности проверьте, что все адаптеры поддерживают требуемые свойства поверхности. Даже если прорисовка выполняется только в один адаптер, окно WPF может быть отображено на любом адаптере в системе. Следует всегда писать код Direct3D9, обрабатывающий конфигурацию с несколькими адаптерами, а также проверять поддержку всех адаптеров, так как WPF может перемещать поверхность между доступными адаптерами.

В следующем примере кода показано, как проверить поддержку 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;
}

Создание поверхности

Перед созданием поверхности проверьте, что возможности устройства поддерживают хорошую производительность в целевой операционной системе. Дополнительные сведения см. в разделе Вопросы производительности, связанные с взаимодействием Direct3D9 и WPF.

После проверки возможностей устройства можно создать поверхность. В следующем примере кода показано, как создать целевой буфер визуализации.

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

В операционной системе Windows Vista, настроенной для использования WDDM, можно создать текстуру целевого буфера визуализации и передать поверхность с уровнем 0 в метод SetBackBuffer. Этот способ не рекомендуется применять в Windows XP, так как нельзя создать блокируемую текстуру целевого буфера визуализации, и производительность будет снижена.

Обработка состояния устройства

При изменении свойства IsFrontBufferAvailable с true на false WPF не отображает D3DImage и не копирует задний буфер в передний. Обычно это означает, что устройство было потеряно.

Если устройство потеряно, код должен остановить визуализацию и ожидать, когда значение свойства IsFrontBufferAvailable будет изменено на true. Обработайте событие IsFrontBufferAvailableChanged, чтобы получить уведомление об этом изменении.

При изменении значения события IsFrontBufferAvailable с false на true следует проверить, что устройство работает.

Для устройств Direct3D9 вызовите метод TestCooperativeLevel. Для устройств Direct3D9Ex вызовите метод CheckDeviceState, так как метод TestCooperativeLevel является устаревшим и всегда возвращает состояние успеха.

Если устройство работает, вызовите снова метод SetBackBuffer с исходной поверхностью.

Если устройство не работает, необходимо переустановить его и повторно создать ресурсы. Вызов метода SetBackBuffer с поверхностью из неработающего устройства создает исключение.

Вызовите метод Reset, чтобы вернуться из неработающего устройства, если реализована поддержка нескольких адаптеров. Иначе освободите все интерфейсы Direct3D9 и полностью их пересоздайте. Если разметка адаптера изменена, объекты Direct3D9, созданные до изменения, не обновляются.

Обработка изменения размера

Если объект D3DImage отображается с разрешением, отличным от исходного размера, он масштабируется в соответствии с текущим BitmapScalingMode, за исключением того, что билинейный режим замещается изображением Fant.

Если требуется более высокая точность, необходимо создать новую поверхность, когда изменяется размер контейнера с D3DImage.

Имеется три способа обработки изменения размера.

  • Обратитесь к системе макета и создайте новую поверхность, если изменяется размер. Не создавайте слишком много поверхностей, так как можно израсходовать или фрагментировать память.

  • Подождите, пока не будет создано событие изменения размера за фиксированный период времени, чтобы создать новую поверхность.

  • Создайте DispatcherTimer, который будет проверять размеры контейнера несколько раз в секунду.

Оптимизация нескольких мониторов

Значительное снижение производительности может произойти при перемещении D3DImage системой визуализации в другой монитор.

В WDDM пока мониторы работают на одной видео карте и используется Direct3DCreate9Ex, снижения производительности не происходит. Производительность снижается, если мониторы используют разные видео карты. В Windows XP производительность снижается всегда.

Если D3DImage перемещается на другой монитор, чтобы восстановить хорошую производительность, можно создать новую поверхность на соответствующем адаптере.

Чтобы избежать снижения производительности, напишите отдельный код для каждого случая с применением нескольких мониторов. В следующем списке показан один способ написания кода для нескольких мониторов.

  1. Найдите точку объекта D3DImage в пространстве экрана с помощью метода Visual.ProjectToScreen.

  2. Используйте метод графического интерфейса MonitorFromPoint, чтобы найти монитор, отображающий эту точку.

  3. Используйте метод IDirect3D9::GetAdapterMonitor, чтобы найти адаптер Direct3D9 этого монитора.

  4. Если используется адаптер, отличный от адаптера заднего буфера, создайте новый задний буфер для нового монитора и назначьте его заднему буферу D3DImage.

ПримечаниеПримечание

Если D3DImage распределяется между несколькими мониторами, производительность будет снижена, за исключением использования WDDM и IDirect3D9Ex на одном адаптере.В этой ситуации нет способа улучшить производительность.

В следующем примере кода показано, как найти текущий монитор.

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

Обновите монитор, если изменяется размер или позиция контейнера D3DImage, или обновите монитор с помощью DispatcherTimer, который обновляется несколько раз в секунду.

Программная визуализация WPF

WPF выполняет визуализацию синхронно для потока данных пользовательского интерфейса в следующих ситуациях.

При возникновении одной из этих ситуаций система визуализации вызывает метод CopyBackBuffer, чтобы скопировать содержимое аппаратного буфера в программу. Реализация по умолчанию вызывает метод GetRenderTargetData с пользовательской поверхностью. Так как этот вызов происходит вне шаблона блокирования и разблокирования, он может завершиться ошибкой. В этом случае, метод CopyBackBuffer возвращает значение null, и изображение не отображается.

Можно переопределить метод CopyBackBuffer, вызвать базовую реализацию и, в случае возврата значения null, вернуть BitmapSource местозаполнителя.

Также можно реализовать собственную программную визуализацию, вместо вызова базовой реализации.

ПримечаниеПримечание

Если WPF выполняет визуализацию полностью в программе, D3DImage не отображается, так как WPF не имеет переднего буфера.

См. также

Задачи

Пошаговое руководство. Создание содержимого Direct3D9 для размещения в WPF

Пошаговое руководство. Размещение содержимого Direct3D9 в WPF

Ссылки

D3DImage

Основные понятия

Вопросы производительности, связанные с взаимодействием Direct3D9 и WPF