Compartilhar via


Interoperação do WPF e direct3D9

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

Observação

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

Exibir Memórias Intermediárias

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 de fundo 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 traseiro e o buffer frontal.

Buffers de exibição D3DImage

Criação de dispositivo Direct3D9

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

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

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

No sistema operacional Windows Vista ou posterior, use o método Direct3DCreate9Ex com uma tela configurada para usar o Modelo de Driver de Exibição do Windows (WDDM). Use o Direct3DCreate9 método em qualquer outra plataforma.

Disponibilidade do método Direct3DCreate9Ex

O d3d9.dll tem o Direct3DCreate9Ex método somente no Windows Vista ou posterior. Se você vincular diretamente a função no Windows XP, o aplicativo não será carregado. Para determinar se o Direct3DCreate9Ex método tem suporte, carregue a DLL e procure o endereço proc. O código a seguir mostra como testar o Direct3DCreate9Ex método. 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 do 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;
}

Apresentar parâmetros

A criação de um dispositivo também requer um D3DPRESENT_PARAMETERS struct, 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 impedir a corrupção da 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 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;
}

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

Para exibir o conteúdo do Direct3D9 em um D3DImage, você cria uma superfície Direct3D9 e a atribui chamando o método SetBackBuffer.

Verificando o suporte ao adaptador

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

O exemplo de código a seguir mostra como verificar todos os adaptadores no sistema para obter suporte ao 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;
}

Criando o Surface

Antes de criar uma superfície, verifique se os recursos do dispositivo dão suporte a um bom desempenho no sistema operacional de destino. Para obter mais informações, consulte Considerações de desempenho para a interoperabilidade do Direct3D9 e do WPF.

Quando você tiver verificado os recursos do dispositivo, poderá 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, pois você não pode criar uma textura de destino de renderização bloqueável e o desempenho será reduzido.

Manipulando o estado do 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 de fundo são copiadas para o buffer frontal quando você chama o Unlock método, onde ele é exibido no hardware. Ocasionalmente, o buffer frontal fica indisponível. Essa falta de disponibilidade pode ser causada pelo bloqueio de tela, aplicativos Direct3D exclusivos em tela inteira, alternância de usuário ou outras atividades do sistema. Quando isso ocorre, seu aplicativo WPF é notificado manipulando o IsFrontBufferAvailableChanged evento. A maneira como seu aplicativo responde ao buffer frontal ficando indisponível depende se o WPF está habilitado para fazer fallback para a renderização de software. O SetBackBuffer método tem uma sobrecarga que usa um parâmetro que especifica se o WPF volta à renderização de software.

Quando você chama a SetBackBuffer(D3DResourceType, IntPtr) sobrecarga ou chama a SetBackBuffer(D3DResourceType, IntPtr, Boolean) sobrecarga com o enableSoftwareFallback parâmetro definido como false, o sistema de renderização libera sua referência ao buffer de fundo quando o buffer frontal fica indisponível e nada é exibido. Quando o buffer frontal está disponível novamente, o sistema de renderização aciona o IsFrontBufferAvailableChanged evento para notificar seu aplicativo WPF. Você pode criar um manipulador de eventos para que o IsFrontBufferAvailableChanged evento reinicie a 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 enableSoftwareFallback parâmetro definido como true, o sistema de renderização mantém sua referência ao buffer de fundo quando o buffer frontal fica indisponível, portanto, não há necessidade de chamar SetBackBuffer quando o buffer frontal estiver disponível novamente.

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

Para se recuperar de um dispositivo inválido, chame o método Reset apenas se você implementar o suporte a vários adaptadores. Caso contrário, libere todas as interfaces Do Direct3D9 e as recinte completamente. Se o layout do adaptador tiver sido alterado, os objetos Direct3D9 criados antes da alteração não serão atualizados.

Manipulando 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 pelo qual Bilinear ele é substituído Fant.

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

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

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

  • Aguarde até que um evento de redimensionamento não tenha ocorrido por um período fixo 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 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 é movido para outro monitor, você pode criar uma nova superfície no adaptador correspondente para restaurar um bom desempenho.

Para evitar a penalidade de desempenho, escreva o 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. Encontre um ponto do D3DImage espaço na 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 método IDirect3D9::GetAdapterMonitor para descobrir em qual adaptador Direct3D9 o monitor está.

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

Observação

Se o D3DImage se estender por monitores, o desempenho será lento, exceto no caso de WDDM e IDirect3D9Ex no mesmo adaptador. Não há como 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 D3DImage tamanho ou a posição do contêiner for alterado ou atualize o monitor usando um DispatcherTimer que é atualizado algumas vezes por segundo.

Renderização de software do WPF

O WPF é renderizado de forma síncrona no thread da interface do usuário no software nas situações a seguir.

Quando uma dessas situações ocorre, o sistema de renderização chama o CopyBackBuffer método para copiar o 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 pode falhar. Nesse caso, o CopyBackBuffer método retorna null e nenhuma imagem é exibida.

Você pode sobrescrever o método CopyBackBuffer, chamar a implementação base, e se ela retornar null, você pode retornar um placeholder BitmapSource.

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

Observação

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

Consulte também