Interoperação Direct3D9 e WPF

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

Observação

Ao usar o conteúdo Direct3D9 no WPF, você também precisa pensar a respeito do desempenho. Para obter mais informações sobre como otimizar o desempenho, consulte Considerações sobre desempenho para interoperabilidade entre Direct3D9 e WPF.

Buffers de exibição

A D3DImage classe gerencia dois buffers de exibição, que são chamados de buffer traseiro e buffer frontal. O buffer de fundo é a superfície do Direct3D9. As alterações no buffer traseiro são copiadas para o buffer frontal quando você chama o Unlock método.

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

D3DImage display buffers

Criação de dispositivo Direct3D9

Para renderizar conteúdo Direct3D9, você deve criar um dispositivo de Direct3D9. Há dois objetos de Direct3D9 que você pode usar para criar um dispositivo: IDirect3D9 e IDirect3D9Ex. Use esses objetos para criar dispositivos IDirect3DDevice9 e IDirect3DDevice9Ex, respectivamente.

Crie um dispositivo chamando um dos métodos a seguir.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

No Windows Vista ou sistemas operacionais mais recentes, use o método Direct3DCreate9Ex com uma exibição que está configurada para usar o WDDM (Windows Display Driver Model). Use o método Direct3DCreate9 em qualquer outra plataforma.

Disponibilidade do método Direct3DCreate9Ex

O d3d9.dll tem o método Direct3DCreate9Ex somente no Windows Vista ou em sistemas operacionais posteriores. Se você vincular diretamente a função no Windows XP, o aplicativo falhará ao carregar. Para determinar se o método Direct3DCreate9Ex tem suporte, carregue a DLL e procure o endereço de proc. O código a seguir mostra como testar o método Direct3DCreate9Ex. Para obter um exemplo de código completo, consulte Passo a passo: criando conteúdo 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ê cria um HWND fictício para ser usado pelo Direct3D9. 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 presentes

A criação de um dispositivo também exige um struct D3DPRESENT_PARAMETERS, mas apenas alguns parâmetros são importantes. Esses parâmetros são escolhidos para minimizar o volume de memória.

Defina os campos BackBufferHeight e BackBufferWidth como 1. Defini-los como 0 faz com que eles sejam definidos para as dimensões do HWND.

Sempre defina os sinalizadores D3DCREATE_MULTITHREADED e D3DCREATE_FPU_PRESERVE para evitar a corrupção de memória usada pelo Direct3D9 e evitar que o Direct3D9 altere as configurações de FPU.

O código a seguir mostra como inicializar o struct 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;
}

Criando o destino de renderização do buffer de fundo

Para exibir conteúdo Direct3D9 em um D3DImage, crie uma superfície Direct3D9 e atribua-a chamando o SetBackBuffer método.

Verificando o suporte do adaptador

Antes de criar uma superfície, verifique se todos os adaptadores dão suporte às propriedades de superfície que você precisa. Mesmo se você renderizar para apenas um adaptador, a janela do WPF poderá ser exibida em qualquer adaptador do sistema. Você sempre deve escrever um código do Direct3D9 que manipule configurações de vários adaptadores e deve verificar o suporte de todos os adaptadores, porque o WPF pode mover a superfície entre os adaptadores disponíveis.

O exemplo de código a seguir mostra como verificar o suporte ao Direct3D9 de todos os adaptadores no 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 dão suporte ao bom desempenho no sistema operacional de destino. Para obter mais informações, consulte Considerações sobre Desempenho para Interoperabilidade entre Direct3D9 e WPF.

Quando os recursos do dispositivo foram verificados, você pode criar a superfície. O exemplo de código a seguir mostra como criar o destino de renderização.

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 e em sistemas operacionais posteriores, configurados 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, porque você não pode criar uma textura de destino de renderização bloqueável e o desempenho será reduzido.

Manipulação de estado de dispositivo

A D3DImage classe gerencia dois buffers de exibição, que são chamados de buffer traseiro e buffer frontal. O buffer de fundo é a superfície do Direct3D. As alterações no buffer traseiro são copiadas para o buffer frontal quando você chama o Unlock método, onde ele é exibido no hardware. Ocasionalmente, o buffer frontal se torna indisponível. Essa falta de disponibilidade pode ser causada por bloqueio de tela, aplicativos Direct3D de uso exclusivo em tela inteira, troca de usuário ou outras atividades do sistema. Quando isso ocorre, seu aplicativo WPF é notificado manipulando o IsFrontBufferAvailableChanged evento. A maneira que seu aplicativo responde à indisponibilidade do buffer frontal depende de se o WPF está habilitado para voltar à renderização de software. O SetBackBuffer método tem uma sobrecarga que usa um parâmetro que especifica se o WPF retorna à renderização de software.

Quando você chama a sobrecarga ou chama a SetBackBuffer(D3DResourceType, IntPtr)SetBackBuffer(D3DResourceType, IntPtr, Boolean) sobrecarga com o parâmetro definido como false, o sistema de renderização libera sua referência ao buffer traseiro quando o enableSoftwareFallback buffer frontal fica indisponível e nada é exibido. Quando o buffer frontal estiver disponível novamente, o sistema de renderização acionará o IsFrontBufferAvailableChanged evento para notificar seu aplicativo WPF. Você pode criar um manipulador de eventos para que o evento reinicie a IsFrontBufferAvailableChanged renderização novamente com uma superfície Direct3D válida. Para reiniciar a renderização, você deve chamar SetBackBuffer.

Quando você chama a SetBackBuffer(D3DResourceType, IntPtr, Boolean) sobrecarga com o parâmetro definido como true, o sistema de renderização mantém sua referência ao buffer traseiro quando o buffer frontal fica indisponível, portanto, não há necessidade de chamar SetBackBuffer quando o enableSoftwareFallback buffer frontal estiver disponível novamente.

Quando a renderização de software é habilitada, pode haver situações em que o dispositivo do usuário fica indisponível, mas o sistema de renderização retém uma referência à superfície do Direct3D. Para verificar se um dispositivo de Direct3D9 está indisponível, chame o método TestCooperativeLevel. Para verificar um dispositivo Direct3D9Ex, chame o método CheckDeviceState, porque o método TestCooperativeLevel foi preterido e sempre retorna êxito. Se o dispositivo do usuário tiver ficado indisponível, chame SetBackBuffer para liberar a referência do WPF para o buffer traseiro. Se você precisar redefinir seu dispositivo, chame com o backBuffer parâmetro definido como nulle, em seguida, chame SetBackBufferSetBackBuffer novamente com definido para backBuffer uma superfície Direct3D válida.

Chame o método Reset para recuperar de um dispositivo inválido somente se você implementar o suporte a vários adaptadores. Caso contrário, libere todas as interfaces de Direct3D9 e recrie-as completamente. Se o layout do adaptador for alterado, os objetos de Direct3D9 criados antes da alteração não serão atualizados.

Lidando com o redimensionamento

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

Se você precisar de maior fidelidade, deverá criar uma nova superfície quando o contêiner do D3DImage alterar de tamanho.

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

  • Participar no sistema de layout e criar uma nova superfície quando o tamanho for alterado. Não crie muitas superfícies, pois você poderá esgotar ou fragmentar a memória de vídeo.

  • Aguardar durante um período fixo de tempo sem que ocorra um evento de redimensionamento para criar a nova superfície.

  • Crie um DispatcherTimer que verifique as dimensões do contêiner várias vezes por segundo.

Otimização de vários monitores

Um desempenho significativamente reduzido pode resultar quando o sistema de renderização move um D3DImage para outro monitor.

No WDDM, desde que os monitores estejam na mesma placa de vídeo e você use o Direct3DCreate9Ex, não haverá redução no desempenho. Se os monitores estiverem em placas de vídeo separadas, o desempenho será reduzido. No Windows XP, o desempenho é sempre reduzido.

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

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

  1. Encontre um ponto do D3DImage espaço na tela com o Visual.ProjectToScreen método.

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

  3. Use o método IDirect3D9::GetAdapterMonitor para localizar em qual adaptador de Direct3D9 o monitor está.

  4. Se o adaptador não for o mesmo que o adaptador com o buffer traseiro, crie um novo buffer traseiro no novo monitor e atribua-o D3DImage ao buffer traseiro.

Observação

Se o straddles monitorar, o D3DImage desempenho será lento, 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;
            }
        }
    }
}

Atualize o monitor quando o tamanho ou a D3DImage posição do contêiner for alterado ou atualize o monitor usando um DispatcherTimer que atualize algumas vezes por segundo.

Renderização de Software do WPF

O WPF renderiza de maneira síncrona no thread da interface do usuário do software nas seguintes situações.

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

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

Você também pode implementar sua própria renderização de software em vez de chamar a implementação de base.

Observação

Se o WPF estiver renderizando completamente no software, D3DImage não será mostrado porque o WPF não tem um buffer frontal.

Confira também