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

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

Примечание.

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

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

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

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

D3DImage display buffers

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

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

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

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

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

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

d3d9.dll содержит метод Direct3DCreate9Ex только в операционной системе 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, и предотвратить изменение параметров FPU в Direct3D9.

В следующем коде показана инициализация структуры 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, так как невозможно создать блокируемый целевой объект отрисовки, и производительность отрисовки будет снижена.

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

Класс D3DImage управляет двумя буферами отображения, которые называются задним ипередним буфером. Задний буфер — это интерфейс Direct3D. Изменения обратного буфера копируются в передний буфер при вызове метода Unlock, где буфер отображается на оборудовании. Иногда передний буфер может стать недоступным. Это может быть вызвано блокировкой экрана, полноэкранными приложениями Direct3D в монопольном режиме, переключением пользователей или другими системными действиями. В этом случае в приложение WPF передается уведомление об этом событии IsFrontBufferAvailableChanged. Приложения не реагирует на передний буфер и становится недоступным. Это зависит от того, включена ли функция WPF для отрисовки программного обеспечения. Метод SetBackBuffer имеет перегрузку, которая принимает параметр, указывающий на то, возвращается ли WPF к программной отрисовке.

При вызове перегрузки SetBackBuffer(D3DResourceType, IntPtr) или перегрузки SetBackBuffer(D3DResourceType, IntPtr, Boolean) с параметром enableSoftwareFallback, установленным в значение false, система отрисовки освобождает ссылку на задний буфер, когда передний буфер становится недоступным, и ничего не отображается. Если передний буфер становится снова доступен, система отрисовки вызывает событие IsFrontBufferAvailableChanged для уведомления приложения WPF. Можно создать обработчик событий для события IsFrontBufferAvailableChanged, чтобы снова перезапустить процесс отрисовки с помощью допустимой поверхности Direct3D. Чтобы перезапустить отрисовку, необходимо вызвать SetBackBuffer.

При вызове перегрузки SetBackBuffer(D3DResourceType, IntPtr, Boolean) с параметром enableSoftwareFallback, установленным в значение true, система отрисовки сохраняет свою ссылку в заднем буфере, когда передний буфер становится недоступным, поэтому, когда передний буфер снова станет доступным вызывать SetBackBuffer не требуется.

Если включена отрисовка программного обеспечения, могут возникнуть ситуации, когда устройство пользователя становится недоступным, но система отрисовки сохраняет ссылку на поверхность Direct3D. Чтобы проверить, недоступно ли устройство Direct3D9, вызовите метод TestCooperativeLevel. Чтобы проверить устройства Direct3D9Ex, вызовите метод CheckDeviceState, так как этот метод TestCooperativeLevel является устаревшим и всегда возвращает успешное выполнение. Если устройство пользователя стало недоступным, вызовите SetBackBuffer для выпуска ссылки WPF на обратный буфер. Если необходимо сбросить устройство, вызовите SetBackBuffer с параметром backBuffer, установленным в значение null, а затем снова SetBackBuffer с backBuffer, установленным в допустимую поверхность Direct3D.

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

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

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

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

Существует три возможных подхода к обработке изменения размера.

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

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

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

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

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

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

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

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

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

  2. Используйте метод GDI 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 не содержит переднего буфера.

См. также