Partilhar via


WPF e a interoperação de Direct3D9

Você pode incluir o conteúdo de Direct3D9 em um aplicativo de Windows Presentation Foundation (WPF). Este tópico descreve como criar conteúdo de Direct3D9, para que ele interopera com eficiência com o WPF.

Observação

Ao usar o conteúdo de Direct3D9 no WPF, você precisa pensar no desempenho.Para obter mais informações sobre como otimizar o desempenho, consulte Considerações sobre o desempenho de Direct3D9 e interoperabilidade com WPF.

Buffers de vídeo

O D3DImage classe gerencia dois buffers de vídeo, que são chamados de buffer de fundo e o buffer frontal. O buffer de fundo é a superfície de Direct3D9. Alterações para o buffer de fundo são copiadas para frente no buffer frontal ao chamar o Unlock método.

A ilustração a seguir mostra a relação entre o buffer de fundo e o buffer frontal.

Buffers de exibição D3DImage

Criação de dispositivos do Direct3D9

Para processar Direct3D9 conteúdo, você deve criar um dispositivo de Direct3D9. Existem dois objetos de Direct3D9 que você pode usar para criar um dispositivo, IDirect3D9 e IDirect3D9Ex. Usar esses objetos para criar IDirect3DDevice9 e IDirect3DDevice9Ex dispositivos, respectivamente.

Crie um dispositivo chamando um dos seguintes métodos.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

Use o Direct3DCreate9Ex método no Windows Vista com uma exibição que está configurado para usar o Windows exibir Driver WDDM (Model). Use o Direct3DCreate9 método em qualquer outra plataforma.

Disponibilidade do método Direct3DCreate9Ex

Somente o D3D9 no Windows Vista tem o Direct3DCreate9Ex método. Se você vincular diretamente a função no Windows XP, seu aplicativo não carrega. Para determinar se o Direct3DCreate9Ex método é suportado, carregar a DLL e procure o endereço de proc. O código a seguir mostra como testar o Direct3DCreate9Ex método. Para obter um exemplo de código completo, consulte Demonstra Passo a passo: A criação de conteúdo de Direct3D9 para hospedagem no 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;
}

Criação de HWND

A criação de um dispositivo requer um HWND. Em geral, você pode criar um HWND fictício de Direct3D9 usar. O exemplo de código a seguir mostra como criar um HWND fictício.

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

Parâmetros de presentes

A criação de um dispositivo também requer um D3DPRESENT_PARAMETERS struct, mas somente alguns parâmetros são importantes. Esses parâmetros são escolhidos para minimizar a superfície de memória.

Definir o BackBufferHeight e BackBufferWidth campos de 1. Defini-las como 0 faz com que eles a ser definida para as dimensões do HWND.

Defina sempre a D3DCREATE_MULTITHREADED e D3DCREATE_FPU_PRESERVE sinalizadores para evitar corrompendo a memória usada pelo Direct3D9 e impedir que Direct3D9 alterando as configurações de FPU.

O código a seguir mostra como inicializar o D3DPRESENT_PARAMETERS struct.

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

Criar o destino de renderização de Buffer Back

Para exibir o conteúdo de Direct3D9 em um D3DImage, você criar uma superfície de Direct3D9 e atribuí-lo chamando o SetBackBuffer método.

Verificando o suporte do adaptador

Antes de criar uma superfície, verifique se todos os adaptadores para suporte a propriedades de superfície que você precisa. Mesmo se você processa apenas um adaptador, a janela WPF pode ser exibida em qualquer adaptador, no sistema. Você sempre deve escrever o código de Direct3D9 que lida com as configurações de multi-adapter e você deve verificar todos os adaptadores para suporte, porque o WPF pode mover a superfície entre os adaptadores disponíveis.

O exemplo de código a seguir mostra como verificar o suportam a todos os adaptadores para Direct3D9 o sistema.

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

Criando a superfície.

Antes de criar uma superfície, verifique se os recursos do dispositivo oferecer suporte a bom desempenho no sistema operacional de destino. For more information, see Considerações sobre o desempenho de Direct3D9 e interoperabilidade com WPF.

Quando você tiver verificado os recursos do dispositivo, você pode criar a superfície. O exemplo de código a seguir mostra como criar o alvo processado.

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

No Windows Vista, o que é configurado para usar o WDDM, você pode criar uma textura de destino de renderização e passar a superfície de nível 0 para o SetBackBuffer método. Essa abordagem não é recomendada no Windows XP, pois não é possível criar uma textura de destino de render bloqueáveis e o desempenho será reduzido.

Estado do dispositivo de tratamento

Quando o IsFrontBufferAvailable transições de propriedade de true para false, WPF não exibe o D3DImage e não copiar o seu buffer de fundo para o buffer frontal. Geralmente, isso significa que o dispositivo foi perdido.

Quando o dispositivo for perdido, o seu código deve parar o processamento e aguarde a IsFrontBufferAvailable propriedade transição para a true. Lidar com o IsFrontBufferAvailableChanged o evento para ser notificado de transição.

Quando o IsFrontBufferAvailable transições de propriedade de false para true, você deve verificar o seu dispositivo para determinar se é válido.

Para dispositivos de Direct3D9, chamar o TestCooperativeLevel método. Para dispositivos de Direct3D9Ex chamada a CheckDeviceState método, porque o TestCooperativeLevel método está obsoleto e sempre retorna sucesso.

Se o dispositivo for válido, chame o SetBackBuffer método novamente com a superfície original.

Se o dispositivo não for válido, você deve redefinir o dispositivo e recriar seus recursos. Chamar o SetBackBuffer método com uma superfície de um dispositivo inválido gera uma exceção.

Chamar o Reset método para recuperar de um dispositivo inválido somente se você implementar suporte em multi-adapter. Caso contrário, todas as interfaces de Direct3D9 de versão e recriá-los completamente. Se o layout do adaptador foi alterado, os objetos de Direct3D9 criados antes da alteração não são atualizados.

Tratamento de redimensionamento

Se um D3DImage é exibido em uma resolução diferente de seu tamanho nativo, ele é dimensionado de acordo com a atual BitmapScalingMode, exceto que Bilinear é substituído para Fant.

Se você precisar de uma alta fidelidade, você deve criar um novo de superfície quando o contêiner da D3DImage alterações de tamanho.

Há três abordagens possíveis para lidar com redimensionamento.

  • Participar do sistema de layout e criar uma nova superfície quando o tamanho alterado. Não crie muitos superfícies, pois você pode usar ou fragmento de memória de vídeo.

  • Aguarde até que um evento resize não ocorreu por um período fixo de tempo para criar a superfície de nova.

  • Criar um DispatcherTimer que verifica as dimensões do recipiente várias vezes por segundo.

Otimização de vários monitores

Redução significativa de desempenho pode resultar quando o sistema de processamento move uma D3DImage para outro monitor.

No WDDM, contanto que os monitores estão no mesmo vídeo cartão e usar Direct3DCreate9Ex, há não há redução no desempenho. Se os monitores em placas de vídeo separadas, o desempenho é reduzido. No Windows XP, o desempenho é sempre reduzido.

Quando o D3DImage se move para outro monitor, você pode criar uma superfície de nova no adaptador correspondente para restaurar um bom desempenho.

Para evitar a degradação de desempenho, escreva código especificamente para o caso de vários monitores. A lista a seguir mostra uma maneira de escrever código de vários monitores.

  1. Localizar um ponto da D3DImage no espaço de tela com o Visual.ProjectToScreen método.

  2. Use o MonitorFromPoint método GDI para localizar o monitor que está exibindo o ponto.

  3. Use o IDirect3D9::GetAdapterMonitor método para localizar o adaptador de Direct3D9 que o monitor é no.

  4. Se o adaptador não é o mesmo que o adaptador com o buffer de fundo, crie um novo buffer de fundo no novo monitor e atribuí-lo para o D3DImage fazer o buffer.

Observação

Se a D3DImage straddles monitores, o desempenho será lentos, exceto no caso do WDDM e IDirect3D9Ex no mesmo adaptador.Não há nenhuma maneira de melhorar o desempenho nessa situação.

O exemplo de código a seguir mostra como localizar o monitor atual.

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

Atualizar o monitor quando o D3DImage alterações de tamanho ou a posição do contêiner ou atualizar o monitor usando um DispatcherTimer que atualiza algumas vezes por segundo.

Processamento de Software do WPF

WPF sincronicamente processa no thread da interface do usuário no software nas seguintes situações.

Quando uma dessas situações ocorre, o sistema de processamento chama o CopyBackBuffer método para copiar o buffer de hardware para software. As chamadas de implementação do padrão de GetRenderTargetData método com sua superfície. Como esta chamada ocorre fora do padrão de bloqueio/desbloqueio, ele poderá falhar. Nesse caso, o CopyBackBuffer método retorna null e nenhuma imagem seja exibida.

Você pode substituir o CopyBackBuffer método, chame a implementação base, e se ela retorna null, você pode retornar um espaço reservado BitmapSource.

Você também pode implementar seu próprio processamento de software em vez de chamar a implementação base.

Observação

Se o WPF é processado completamente no software, D3DImage não é mostrado porque o WPF não tem um buffer frontal.

Consulte também

Tarefas

Demonstra Passo a passo: A criação de conteúdo de Direct3D9 para hospedagem no WPF

Demonstra Passo a passo: Hospedagem de conteúdo de Direct3D9 no WPF

Referência

D3DImage

Conceitos

Considerações sobre o desempenho de Direct3D9 e interoperabilidade com WPF