Share via


Interoperabilidade entre DirectX e XAML

Observação

Este tópico se aplica a jogos e aplicativos de Plataforma Universal do Windows (UWP) e a tipos nos namespaces Windows.UI.Xaml.Xxx (não Microsoft.UI.Xaml.Xxx).

Você pode usar a XAML (Extensible Application Markup Language) junto com o Microsoft DirectX em seu jogo ou aplicativo de Plataforma Universal do Windows (UWP). A combinação de XAML e DirectX permite criar estruturas flexíveis de interface do usuário que interoperam com seu conteúdo renderizado pelo DirectX; que é particularmente útil para aplicativos com uso intensivo de gráficos. Este tópico explica a estrutura de um aplicativo UWP que usa DirectX e identifica os tipos importantes a serem usados ao criar seu aplicativo UWP para trabalhar com o DirectX.

Se seu aplicativo se concentrar principalmente na renderização 2D, talvez você queira usar a biblioteca de Windows Runtime Win2D. Essa biblioteca é mantida pela Microsoft e é criada com base na principal tecnologia de Direct2D. O Win2D simplifica muito o padrão de uso para implementar elementos gráficos 2D e inclui abstrações úteis para algumas das técnicas descritas neste documento. Consulte a página de projeto para obter mais detalhes. Esse documento aborda orientações para desenvolvedores de aplicativos que optam por não usar Win2D.

Observação

As APIs Do DirectX não são definidas como tipos de Windows Runtime, mas você pode usar C++/WinRT para desenvolver aplicativos UWP XAML que interoperam com o DirectX. Se você considerar o código que chama DirectX em seu próprio componente de Windows Runtime C++/WinRT (WRC), poderá usar esse WRC em um aplicativo UWP (até mesmo um C#) que combina XAML e DirectX.

XAML e DirectX

O DirectX fornece duas bibliotecas poderosas para elementos gráficos 2D e 3D, respectivamente: Direct2D e Direct3D. Embora o XAML forneça suporte para primitivos e efeitos 2D básicos, muitos aplicativos de modelagem e jogos precisam de suporte gráficos mais complexos. Para isso, você pode usar Direct2D e Direct3D para renderizar os elementos gráficos mais complexos e usar XAML para elementos de interface do usuário mais tradicionais.

Se você estiver implementando a interoperabilidade XAML e DirectX personalizada, precisará conhecer esses dois conceitos.

  • As superfícies compartilhadas são regiões dimensionadas da exibição, definidas por XAML, que você pode usar o DirectX para desenhar indiretamente usando os tipos Windows::UI::Xaml::Media::ImageSource . Para superfícies compartilhadas, você não controla o tempo preciso de quando o novo conteúdo aparece na tela. Em vez disso, as atualizações na superfície compartilhada são sincronizadas com as atualizações da estrutura XAML.
  • Uma cadeia de troca representa uma coleção de buffers que são usados para exibir gráficos com latência mínima. Normalmente, uma cadeia de troca é atualizada em 60 quadros por segundo separadamente do thread da interface do usuário. No entanto, uma cadeia de troca usa mais recursos de memória e CPU para dar suporte a atualizações rápidas e é relativamente difícil de usar, pois você precisa gerenciar vários threads.

Considere para que você está usando o DirectX. Você o usará para compor ou animar um único controle que se encaixe nas dimensões da janela de exibição? Ela conterá a saída que precisa ser renderizada e controlada em tempo real, como em um jogo? Nesse caso, você provavelmente precisará implementar uma cadeia de troca. Caso contrário, você deverá estar bem usando uma superfície compartilhada.

Depois de determinar como pretende usar o DirectX, use um dos seguintes tipos de Windows Runtime para incorporar a renderização do DirectX em seu aplicativo UWP.

  • Se você quiser compor uma imagem estática ou desenhar uma imagem complexa em intervalos controlados por eventos, desenhe para uma superfície compartilhada com Windows::UI::Xaml::Media::Imaging::SurfaceImageSource. Esse tipo manipula uma superfície de desenho DirectX de tamanho. Normalmente, você usa esse tipo ao compor uma imagem ou textura como um bitmap para exibição em um documento ou elemento da interface do usuário. Ele não funciona bem para interatividade em tempo real, como para um jogo de alto desempenho. Isso ocorre porque as atualizações de um objeto SurfaceImageSource são sincronizadas com atualizações de interface do usuário XAML e podem introduzir latência nos comentários visuais que você fornece ao usuário, como uma taxa de quadros flutuante ou uma resposta incorreta percebida à entrada em tempo real. Atualizações ainda são rápidos o suficiente para controles dinâmicos ou simulações de dados.
  • Se a imagem for maior que o imóvel de tela fornecido e puder ser panorâmica ou ampliada pelo usuário, use Windows::UI::Xaml::Media::Imaging::VirtualSurfaceImageSource. Esse tipo manipula uma superfície de desenho DirectX de tamanho maior que a tela. Assim como o SurfaceImageSource, você usa isso ao redigir uma imagem complexa ou controlar dinamicamente. E, assim como SurfaceImageSource, ele não funciona bem para jogos de alto desempenho. Alguns exemplos de elementos XAML que podem usar um VirtualSurfaceImageSource são controles de mapa ou um visualizador de documentos para imagens grandes e densas.
  • Se você estiver usando o DirectX para apresentar gráficos atualizados em tempo real ou em uma situação em que as atualizações devem vir em intervalos regulares de baixa latência, use a classe SwapChainPanel , para que você possa atualizar os gráficos sem sincronizar com o temporizador de atualização da estrutura XAML. Com SwapChainPanel , você pode acessar diretamente a cadeia de troca do dispositivo gráfico (IDXGISwapChain1) e a camada XAML sobre o destino de renderização. SwapChainPanel funciona muito bem para jogos e aplicativos DirectX em tela inteira que exigem uma interface do usuário baseada em XAML. Você deve conhecer bem o DirectX para usar essa abordagem, incluindo as tecnologias DXGI (Infraestrutura Gráfica) do Microsoft DirectX, Direct2D e Direct3D. Para saber mais, consulte Guia de programação para Direct3D 11.

SurfaceImageSource

O SurfaceImageSource fornece uma superfície compartilhada DirectX para desenhar; em seguida, ele compõe os bits no conteúdo do aplicativo.

Dica

Os aplicativos de exemplo Espaçamento de linha (DirectWrite) e fontes para download (DirectWrite) demonstram o SurfaceImageSource.

Em um nível muito alto, aqui está o processo para criar e atualizar um SurfaceImageSource.

  • Crie um dispositivo Direct 3D, um dispositivo Direct 2D e um contexto de dispositivo Direct 2D.
  • Crie um SurfaceImageSource e defina o dispositivo Direct 2D (ou Direct 3D) nele.
  • Comece a desenhar no SurfaceImageSource para obter uma superfície DXGI.
  • Desenhe para a superfície DXGI com Direct2D (ou Direct3D).
  • Finalize o desenho no SurfaceImageSource quando terminar.
  • Defina o SurfaceImageSource em uma imagem XAML ou ImageBrush para exibi-lo na interface do usuário XAML.

E aqui está um aprofundamento nessas etapas, com exemplos de código-fonte.

  1. Você pode acompanhar o código mostrado e descrito abaixo criando um novo projeto no Microsoft Visual Studio. Criar um projeto de aplicativo em branco (C++/WinRT ). Direcione a versão mais recente em disponibilidade geral (ou seja, que não esteja em versão prévia) do SDK do Windows.

  2. Abra pch.he adicione as seguintes inclusões abaixo das que já estão lá.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    #include <winrt/Windows.UI.Xaml.Media.Imaging.h>
    
  3. Adicione a using diretiva mostrada abaixo à parte superior de MainPage.cpp, abaixo das já existentes. Também no MainPage.cpp, substitua a implementação existente de MainPage::ClickHandler pela listagem mostrada abaixo. O código cria um dispositivo Direct 3D, um dispositivo Direct 2D e um contexto de dispositivo Direct 2D. Para fazer isso, ele chama D3D11CreateDevice, D2D1CreateDevice e ID2D1Device::CreateDeviceContext.

    // MainPage.cpp | paste this below the existing using directives
    using namespace Windows::UI::Xaml::Media::Imaging;
    
    // MainPage.cpp | paste this to replace the existing MainPage::ClickHandler
    void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
    {
        myButton().Content(box_value(L"Clicked"));
    
        uint32_t creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    
        D3D_FEATURE_LEVEL featureLevels[] =
        {
            D3D_FEATURE_LEVEL_11_1,
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_1,
            D3D_FEATURE_LEVEL_10_0,
            D3D_FEATURE_LEVEL_9_3,
            D3D_FEATURE_LEVEL_9_2,
            D3D_FEATURE_LEVEL_9_1
        };
    
        // Create the Direct3D device.
        winrt::com_ptr<::ID3D11Device> d3dDevice;
        D3D_FEATURE_LEVEL supportedFeatureLevel;
        winrt::check_hresult(::D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_HARDWARE,
            0,
            creationFlags,
            featureLevels,
            ARRAYSIZE(featureLevels),
            D3D11_SDK_VERSION,
            d3dDevice.put(),
            &supportedFeatureLevel,
            nullptr)
        );
    
        // Get the DXGI device.
        winrt::com_ptr<::IDXGIDevice> dxgiDevice{
            d3dDevice.as<::IDXGIDevice>() };
    
        // Create the Direct2D device and a corresponding context.
        winrt::com_ptr<::ID2D1Device> d2dDevice;
        ::D2D1CreateDevice(dxgiDevice.get(), nullptr, d2dDevice.put());
    
        winrt::com_ptr<::ID2D1DeviceContext> d2dDeviceContext;
        winrt::check_hresult(
            d2dDevice->CreateDeviceContext(
                D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                d2dDeviceContext.put()
            )
        );
    }
    
  4. Em seguida, adicione o código para criar um SurfaceImageSource e defina o dispositivo Direct 2D (ou Direct 3D) nele chamando ISurfaceImageSourceNativeWithD2D::SetDevice.

    Observação

    Se você estiver desenhando para o SurfaceImageSource de um thread em segundo plano, também precisará garantir que o dispositivo DXGI tenha acesso multi-threaded habilitado (conforme mostrado no código abaixo). Por motivos de desempenho, você deve fazer isso se estiver desenhando de um thread em segundo plano.

    Defina o tamanho da superfície compartilhada passando a altura e a largura para o construtor SurfaceImageSource . Você também pode indicar se a superfície precisa de suporte alfa (opacidade).

    Para definir o dispositivo e executar as operações de desenho, precisaremos de um ponteiro para ISurfaceImageSourceNativeWithD2D. Para obter um, consulte o objeto SurfaceImageSource para sua interface ISurfaceImageSourceNativeWithD2D subjacente.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    SurfaceImageSource surfaceImageSource(500, 500);
    
    winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> sisNativeWithD2D{
        surfaceImageSource.as<::ISurfaceImageSourceNativeWithD2D>() };
    
    // Associate the Direct2D device with the SurfaceImageSource.
    sisNativeWithD2D->SetDevice(d2dDevice.get());
    
    // To enable multi-threaded access (optional)
    winrt::com_ptr<::ID3D11Multithread> d3dMultiThread{
        d3dDevice.as<::ID3D11Multithread>() };
    d3dMultiThread->SetMultithreadProtected(true);
    
  5. Chame ISurfaceImageSourceNativeWithD2D::BeginDraw para recuperar uma superfície DXGI (uma interface IDXGISurface ). Você pode chamar ISurfaceImageSourceNativeWithD2D::BeginDraw (e os comandos de desenho posteriores) de um thread em segundo plano se tiver habilitado o acesso multi-threaded. Nesta etapa, você também cria um bitmap da superfície DXGI e o define no contexto do dispositivo Direct 2D.

    No parâmetro offset , ISurfaceImageSourceNativeWithD2D::BeginDraw retorna o deslocamento de ponto (um valor x,y) do retângulo de destino atualizado. Você pode usar esse deslocamento para determinar onde desenhar seu conteúdo atualizado com o ID2D1DeviceContext.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    winrt::com_ptr<::IDXGISurface> dxgiSurface;
    
    RECT updateRect{ 0, 0, 500, 500 };
    POINT offset{ 0, 0 };
    HRESULT beginDrawHR = sisNativeWithD2D->BeginDraw(
        updateRect,
        __uuidof(::IDXGISurface),
        dxgiSurface.put_void(),
        &offset);
    
    // Create render target.
    winrt::com_ptr<::ID2D1Bitmap1> bitmap;
    winrt::check_hresult(
        d2dDeviceContext->CreateBitmapFromDxgiSurface(
            dxgiSurface.get(),
            nullptr,
            bitmap.put()
        )
    );
    
    // Set context's render target.
    d2dDeviceContext->SetTarget(bitmap.get());
    
  6. Use o contexto do dispositivo Direct 2D para desenhar o conteúdo do SurfaceImageSource. Somente a área especificada para atualização na etapa anterior no parâmetro updateRect é desenhada.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED ||
        beginDrawHR == DXGI_ERROR_DEVICE_RESET ||
        beginDrawHR == D2DERR_RECREATE_TARGET)
    {
        // The Direct3D and Direct2D devices were lost and need to be re-created.
        // Recovery steps are:
        // 1) Re-create the Direct3D and Direct2D devices
        // 2) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the new Direct2D
        //    device
        // 3) Redraw the contents of the SurfaceImageSource
    }
    else if (beginDrawHR == E_SURFACE_CONTENTS_LOST)
    {
        // The devices were not lost but the entire contents of the surface
        // were. Recovery steps are:
        // 1) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the Direct2D 
        //    device again
        // 2) Redraw the entire contents of the SurfaceImageSource
    }
    else
    {
        // Draw using Direct2D context.
        d2dDeviceContext->BeginDraw();
    
        d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Orange));
    
        winrt::com_ptr<::ID2D1SolidColorBrush> brush;
        winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(
            D2D1::ColorF(D2D1::ColorF::Chocolate),
            D2D1::BrushProperties(0.8f),
            brush.put()));
    
        D2D1_SIZE_F const size{ 500, 500 };
        D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
        d2dDeviceContext->DrawRectangle(rect, brush.get(), 100.0f);
    
        d2dDeviceContext->EndDraw();
    }
    
  7. Chame ISurfaceImageSourceNativeWithD2D::EndDraw para concluir o bitmap (você deve chamar ISurfaceImageSourceNativeWithD2D::EndDraw somente do thread da interface do usuário). Em seguida, defina o SurfaceImageSource em uma imagem XAML (ou ImageBrush) para exibi-lo na interface do usuário XAML.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    sisNativeWithD2D->EndDraw();
    
    // The SurfaceImageSource object's underlying 
    // ISurfaceImageSourceNativeWithD2D object will contain the completed bitmap.
    
    theImage().Source(surfaceImageSource);
    

    Observação

    Chamar SurfaceImageSource::SetSource (herdado de IBitmapSource::SetSource) gera atualmente uma exceção. Não chame isso do objeto SurfaceImageSource .

    Observação

    Evite desenhar no SurfaceImageSource enquanto a Janela estiver oculta ou inativa, caso contrário, as APIs ISurfaceImageSourceNativeWithD2D falharão. Manipule eventos em torno da visibilidade da janela e da suspensão do aplicativo para fazer isso.

  8. Por fim, adicione o seguinte elemento Image dentro da marcação XAML existente em MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  9. Agora você pode criar e executar o aplicativo. Clique no botão para ver o conteúdo do SurfaceImageSource exibido na Imagem.

    Um contorno retângulo laranja escuro e espesso contra um fundo laranja mais claro

VirtualSurfaceImageSource

VirtualSurfaceImageSource estende SurfaceImageSource, e é para cenários em que o conteúdo é potencialmente grande demais para caber na tela de uma só vez (e/ou muito grande para caber na memória de vídeo como uma única textura) e, portanto, o conteúdo deve ser virtualizado para renderizar de forma ideal. Por exemplo, mapeando aplicativos ou telas de documentos grandes.

Dica

O aplicativo de exemplo de escrita à tinta complexa demonstra VirtualSurfaceImageSource.

VirtualSurfaceImageSource difere do SurfaceImageSource , pois usa um retorno de chamada — IVirtualSurfaceImageSourceCallbacksNative::UpdatesNeeded — que você implementa para atualizar regiões da superfície conforme elas ficam visíveis na tela. Você não precisa limpar regiões ocultas, pois a estrutura XAML cuida disso para você.

É uma boa ideia familiarizar-se com o SurfaceImageSource (consulte a seção SurfaceImageSource acima) antes de enfrentar VirtualSurfaceImageSource. Mas em um nível muito alto, aqui está o processo para criar e atualizar um VirtualSurfaceImageSource.

  • Implemente a interface IVirtualSurfaceImageSourceCallbackNative .
  • Crie um dispositivo Direct 3D, um dispositivo Direct 2D e um contexto de dispositivo Direct 2D.
  • Crie um VirtualSurfaceImageSource e defina o dispositivo Direct 2D (ou Direct 3D) nele.
  • Chame RegisterForUpdatesNeeded no VirtualSurfaceImageSource.
  • Em seu retorno de chamada UpdatesNeeded , chame GetUpdateRectCount e GetUpdateRects.
  • Renderize os retângulos de atualização (usando BeginDraw/EndDraw , assim como para SurfaceImageSource).
  • Defina o SurfaceImageSource em uma imagem XAML ou ImageBrush para exibi-lo na interface do usuário XAML.

E aqui está um aprofundamento nessas etapas, com exemplos de código-fonte.

  1. Você pode acompanhar o código mostrado e descrito abaixo criando um novo projeto no Microsoft Visual Studio. Crie um projeto de Aplicativo em Branco (C++/WinRT) e nomeie-o como VSISDemo (é importante dar esse nome ao projeto se você estiver colando cópias nas listagens de código fornecidas abaixo). Direcione a versão mais recente em disponibilidade geral (ou seja, que não esteja em versão prévia) do SDK do Windows.

  2. Abra pch.he adicione as seguintes inclusões abaixo das que já estão lá.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    #include <winrt/Windows.UI.Xaml.Media.Imaging.h>
    
  3. Nesta etapa, você fornecerá uma implementação da interface IVirtualSurfaceUpdatesCallbackNative . Adicione um novo item de Arquivo de Cabeçalho (.h) ao projeto e nomeie-o como CallbackImplementation.h. Substitua o conteúdo desse arquivo pela listagem abaixo. O código é explicado após a listagem.

    #include "pch.h"
    
    namespace winrt::VSISDemo::implementation
    {
        struct CallbackImplementation : winrt::implements<CallbackImplementation, ::IVirtualSurfaceUpdatesCallbackNative>
        {
            CallbackImplementation(
                winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> sisNativeWithD2D,
                winrt::com_ptr<::IVirtualSurfaceImageSourceNative> const& vsisNative,
                winrt::com_ptr<::ID2D1DeviceContext> const& d2dDeviceContext) :
                m_sisNativeWithD2D(sisNativeWithD2D),
                m_vsisNative(vsisNative),
                m_d2dDeviceContext(d2dDeviceContext)
            {}
    
            IFACEMETHOD(UpdatesNeeded)()
            {
                HRESULT hr = S_OK;
    
                ULONG drawingBoundsCount = 0;
                m_vsisNative->GetUpdateRectCount(&drawingBoundsCount);
    
                std::unique_ptr<RECT[]> drawingBounds(
                    new RECT[drawingBoundsCount]);
    
                m_vsisNative->GetUpdateRects(
                    drawingBounds.get(),
                    drawingBoundsCount);
    
                for (ULONG i = 0; i < drawingBoundsCount; ++i)
                {
                    winrt::com_ptr<::IDXGISurface> dxgiSurface;
    
                    POINT offset{ 0, 0 };
                    HRESULT beginDrawHR = m_sisNativeWithD2D->BeginDraw(
                        drawingBounds[i],
                        __uuidof(::IDXGISurface),
                        dxgiSurface.put_void(),
                        &offset);
    
                    // Create render target.
                    winrt::com_ptr<::ID2D1Bitmap1> bitmap;
                    winrt::check_hresult(
                        m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
                            dxgiSurface.get(),
                            nullptr,
                            bitmap.put()
                        )
                    );
    
                    // Set context's render target.
                    m_d2dDeviceContext->SetTarget(bitmap.get());
    
                    if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED ||
                        beginDrawHR == DXGI_ERROR_DEVICE_RESET ||
                        beginDrawHR == D2DERR_RECREATE_TARGET)
                    {
                        // The Direct3D and Direct2D devices were lost and need to be re-created.
                        // Recovery steps are:
                        // 1) Re-create the Direct3D and Direct2D devices
                        // 2) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the new Direct2D
                        //    device
                        // 3) Redraw the contents of the SurfaceImageSource
                    }
                    else if (beginDrawHR == E_SURFACE_CONTENTS_LOST)
                    {
                        // The devices were not lost but the entire contents of the surface
                        // were. Recovery steps are:
                        // 1) Call ISurfaceImageSourceNativeWithD2D::SetDevice with the Direct2D 
                        //    device again
                        // 2) Redraw the entire contents of the SurfaceImageSource
                    }
                    else
                    {
                        // Draw using Direct2D context.
                        m_d2dDeviceContext->BeginDraw();
    
                        m_d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Orange));
    
                        winrt::com_ptr<::ID2D1SolidColorBrush> brush;
                        winrt::check_hresult(m_d2dDeviceContext->CreateSolidColorBrush(
                            D2D1::ColorF(D2D1::ColorF::Chocolate),
                            D2D1::BrushProperties(0.8f),
                            brush.put()));
    
                        D2D1_SIZE_F const size{ drawingBounds[i].right - drawingBounds[i].left, drawingBounds[i].bottom - drawingBounds[i].top };
                        D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
                        m_d2dDeviceContext->DrawRectangle(rect, brush.get(), 100.0f);
    
                        m_d2dDeviceContext->EndDraw();
                    }
    
                    m_sisNativeWithD2D->EndDraw();
                }
    
                return hr;
            }
    
        private:
            winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> m_sisNativeWithD2D{ nullptr };
            winrt::com_ptr<::IVirtualSurfaceImageSourceNative> m_vsisNative{ nullptr };
            winrt::com_ptr<::ID2D1DeviceContext> m_d2dDeviceContext{ nullptr };
        };
    }
    

    Sempre que uma região do VirtualSurfaceImageSource precisar ser atualizada, a estrutura chamará sua implementação de IVirtualSurfaceUpdatesCallbackNative::UpdatesNeeded (mostrado acima).

    Isso pode acontecer quando a estrutura determina que a região precisa ser desenhada (quando o usuário faz panorâmicas ou amplia a exibição da superfície, por exemplo), ou depois que seu aplicativo tiver chamado IVirtualSurfaceImageSourceNative::Invalidate nessa região.

    Na implementação de IVirtualSurfaceImageSourceNative::UpdatesNeeded, use os métodos IVirtualSurfaceImageSourceNative::GetUpdateRectCount e IVirtualSurfaceImageSourceNative::GetUpdateRects para determinar quais regiões da superfície devem ser desenhadas.

    Para cada região que deve ser atualizada, desenhe o conteúdo específico para essa região, mas restrinja seu desenho às regiões limitadas para melhor desempenho. As especificidades de chamar os métodos ISurfaceImageSourceNativeWithD2D são as mesmas do SurfaceImageSource (consulte a seção SurfaceImageSource acima).

    Observação

    Evite desenhar para VirtualSurfaceImageSource enquanto sua Janela estiver oculta ou inativa, caso contrário, as APIs ISurfaceImageSourceNativeWithD2D falharão. Manipule eventos em torno da visibilidade da janela e da suspensão do aplicativo para fazer isso.

  4. Na classe MainPage , adicionaremos um membro do tipo CallbackImplementation. Também criaremos um dispositivo Direct 3D, um dispositivo Direct 2D e um contexto de dispositivo Direct 2D. Para fazer isso, chamaremos D3D11CreateDevice, D2D1CreateDevice e ID2D1Device::CreateDeviceContext.

    Substitua o conteúdo de MainPage.idl, MainPage.he MainPage.cpp pelo conteúdo das listagens abaixo.

    // MainPage.idl
    namespace VSISDemo
    {
        [default_interface]
        runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
        {
            MainPage();
        }
    }
    
    // MainPage.h
    #pragma once
    
    #include "MainPage.g.h"
    #include "CallbackImplementation.h"
    
    namespace winrt::VSISDemo::implementation
    {
        struct MainPage : MainPageT<MainPage>
        {
            MainPage();
            void ClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args);
    
        private:
            winrt::com_ptr<::IVirtualSurfaceUpdatesCallbackNative> m_cbi{ nullptr };
        };
    }
    
    namespace winrt::VSISDemo::factory_implementation
    {
        struct MainPage : MainPageT<MainPage, implementation::MainPage>
        {
        };
    }
    
    // MainPage.cpp
    #include "pch.h"
    #include "MainPage.h"
    #include "MainPage.g.cpp"
    
    using namespace winrt;
    using namespace Windows::UI::Xaml;
    using namespace Windows::UI::Xaml::Media::Imaging;
    
    namespace winrt::VSISDemo::implementation
    {
        MainPage::MainPage()
        {
            InitializeComponent();
        }
    
        void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
        {
            myButton().Content(box_value(L"Clicked"));
    
            uint32_t creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    
            D3D_FEATURE_LEVEL featureLevels[] =
            {
                D3D_FEATURE_LEVEL_11_1,
                D3D_FEATURE_LEVEL_11_0,
                D3D_FEATURE_LEVEL_10_1,
                D3D_FEATURE_LEVEL_10_0,
                D3D_FEATURE_LEVEL_9_3,
                D3D_FEATURE_LEVEL_9_2,
                D3D_FEATURE_LEVEL_9_1
            };
    
            // Create the Direct3D device.
            winrt::com_ptr<::ID3D11Device> d3dDevice;
            D3D_FEATURE_LEVEL supportedFeatureLevel;
            winrt::check_hresult(::D3D11CreateDevice(
                nullptr,
                D3D_DRIVER_TYPE_HARDWARE,
                0,
                creationFlags,
                featureLevels,
                ARRAYSIZE(featureLevels),
                D3D11_SDK_VERSION,
                d3dDevice.put(),
                &supportedFeatureLevel,
                nullptr)
            );
    
            // Get the Direct3D device.
            winrt::com_ptr<::IDXGIDevice> dxgiDevice{
                d3dDevice.as<::IDXGIDevice>() };
    
            // Create the Direct2D device and a corresponding context.
            winrt::com_ptr<::ID2D1Device> d2dDevice;
            ::D2D1CreateDevice(dxgiDevice.get(), nullptr, d2dDevice.put());
    
            winrt::com_ptr<::ID2D1DeviceContext> d2dDeviceContext;
            winrt::check_hresult(
                d2dDevice->CreateDeviceContext(
                    D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                    d2dDeviceContext.put()
                )
            );
        }
    }
    
  5. Em seguida, adicione código para criar um VirtualSurfaceImageSource com o tamanho desejado e defina o dispositivo Direct 2D (ou Direct 3D) nesse local chamando ISurfaceImageSourceNativeWithD2D::SetDevice.

    Observação

    Se você estiver desenhando para seu VirtualSurfaceImageSource de um thread em segundo plano, também precisará garantir que o dispositivo DXGI tenha acesso multi threaded habilitado (conforme mostrado no código abaixo). Por motivos de desempenho, você deve fazer isso se estiver desenhando de um thread em segundo plano.

    Para definir o dispositivo e executar as operações de desenho, precisaremos de um ponteiro para ISurfaceImageSourceNativeWithD2D. Para obter um, consulte o objeto VirtualSurfaceImageSource para sua interface ISurfaceImageSourceNativeWithD2D subjacente.

    Consulte também IVirtualSurfaceImageSourceNative e chame IVirtualSurfaceImageSourceNative::RegisterForUpdatesNeeded, fornecendo sua implementação de IVirtualSurfaceUpdatesCallbackNative.

    Em seguida, defina o SurfaceImageSource em uma imagem XAML (ou ImageBrush) para exibi-lo na interface do usuário XAML.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    VirtualSurfaceImageSource virtualSurfaceImageSource(2000, 2000);
    
    winrt::com_ptr<::ISurfaceImageSourceNativeWithD2D> sisNativeWithD2D{
        virtualSurfaceImageSource.as<::ISurfaceImageSourceNativeWithD2D>() };
    
    // Associate the Direct2D device with the SurfaceImageSource.
    sisNativeWithD2D->SetDevice(d2dDevice.get());
    
    // To enable multi-threaded access (optional)
    winrt::com_ptr<::ID3D11Multithread> d3dMultiThread{
        d3dDevice.as<::ID3D11Multithread>() };
    d3dMultiThread->SetMultithreadProtected(true);
    
    winrt::com_ptr<::IVirtualSurfaceImageSourceNative> vsisNative{
        virtualSurfaceImageSource.as<::IVirtualSurfaceImageSourceNative>() };
    
    m_cbi = winrt::make<CallbackImplementation>(sisNativeWithD2D, vsisNative, d2dDeviceContext);
    vsisNative->RegisterForUpdatesNeeded(m_cbi.as<::IVirtualSurfaceUpdatesCallbackNative>().get());
    
    // The SurfaceImageSource object's underlying 
    // ISurfaceImageSourceNativeWithD2D object will contain the completed bitmap.
    
    theImage().Source(virtualSurfaceImageSource);
    
  6. Por fim, adicione o seguinte elemento Image dentro da marcação XAML existente em MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  7. Agora você pode criar e executar o aplicativo. Clique no botão para ver o conteúdo do VirtualSurfaceImageSource exibido na Imagem.

SwapChainPanel e jogos

SwapChainPanel é o tipo Windows Runtime projetado para dar suporte a elementos gráficos e jogos de alto desempenho, em que você gerencia a cadeia de troca diretamente. Nesse caso, você cria a sua própria cadeia de troca do DirectX e gerencia a apresentação do seu conteúdo renderizado. Outro recurso do SwapChainPanel é que você pode sobrepor outros elementos XAML na frente dele.

Para garantir um bom desempenho, há certas limitações para o tipo SwapChainPanel .

  • Não deve haver mais de 4 instâncias SwapChainPanel por aplicativo.
  • Você deve definir a altura e a largura da cadeia de troca do DirectX (em DXGI_SWAP_CHAIN_DESC1) para as dimensões atuais do elemento de cadeia de troca. Se você não fizer isso, o conteúdo de exibição será dimensionado para caber (usando DXGI_SCALING_STRETCH).
  • Você deve definir o modo de dimensionamento da cadeia de troca do DirectX (em DXGI_SWAP_CHAIN_DESC1) como DXGI_SCALING_STRETCH.
  • Você deve criar a cadeia de troca do DirectX chamando IDXGIFactory2::CreateSwapChainForComposition.

Atualize o SwapChainPanel com base nas necessidades do seu aplicativo e não sincronize com as atualizações da estrutura XAML. Se você precisar sincronizar as atualizações do SwapChainPanel com as da estrutura XAML, registre-se no evento Windows::UI::Xaml::Media::CompositionTarget::Rendering . Caso contrário, será necessário levar em consideração todos os problemas entre threads se você tentar atualizar os elementos XAML de um thread diferente daquele que atualiza SwapChainPanel.

Se você precisar receber entrada de ponteiro de baixa latência para seu SwapChainPanel, use SwapChainPanel::CreateCoreIndependentInputSource. Esse método retorna um objeto CoreIndependentInputSource que pode ser usado para receber eventos de entrada com latência mínima em um thread em segundo plano. Observe que, depois que esse método for chamado, os eventos de entrada de ponteiro XAML normais não serão gerados para o SwapChainPanel, pois todas as entradas serão redirecionadas para o thread em segundo plano.

Este é o processo para criar e atualizar um objeto SwapChainPanel .

  1. Você pode acompanhar o código mostrado e descrito abaixo criando um novo projeto no Microsoft Visual Studio. Crie um projeto de Aplicativo em Branco (C++/WinRT) e nomeie-o como SCPDemo (é importante dar esse nome ao projeto se você estiver colando cópias nas listagens de código fornecidas abaixo). Direcione a versão mais recente em disponibilidade geral (ou seja, que não esteja em versão prévia) do SDK do Windows.

  2. Abra pch.he adicione as seguintes inclusões abaixo das que já estão lá.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    
  3. Na classe MainPage , primeiro criaremos um dispositivo Direct 3D, um dispositivo Direct 2D e um contexto de dispositivo Direct 2D. Para fazer isso, chamaremos D3D11CreateDevice, D2D1CreateDevice e ID2D1Device::CreateDeviceContext.

    Substitua o conteúdo de MainPage.idl, MainPage.he MainPage.cpp pelo conteúdo das listagens abaixo.

    // MainPage.idl
    namespace SCPDemo
    {
        [default_interface]
        runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
        {
            MainPage();
        }
    }
    
    // MainPage.h
    #pragma once
    
    #include "MainPage.g.h"
    
    namespace winrt::SCPDemo::implementation
    {
        struct MainPage : MainPageT<MainPage>
        {
            MainPage();
            void ClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args);
        };
    }
    
    namespace winrt::SCPDemo::factory_implementation
    {
        struct MainPage : MainPageT<MainPage, implementation::MainPage>
        {
        };
    }
    
    // MainPage.cpp
    #include "pch.h"
    #include "MainPage.h"
    #include "MainPage.g.cpp"
    
    using namespace winrt;
    using namespace Windows::UI::Xaml;
    
    namespace winrt::SCPDemo::implementation
    {
        MainPage::MainPage()
        {
            InitializeComponent();
        }
    
        void MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
        {
            myButton().Content(box_value(L"Clicked"));
    
            uint32_t creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    
            D3D_FEATURE_LEVEL featureLevels[] =
            {
                D3D_FEATURE_LEVEL_11_1,
                D3D_FEATURE_LEVEL_11_0,
                D3D_FEATURE_LEVEL_10_1,
                D3D_FEATURE_LEVEL_10_0,
                D3D_FEATURE_LEVEL_9_3,
                D3D_FEATURE_LEVEL_9_2,
                D3D_FEATURE_LEVEL_9_1
            };
    
            // Create the Direct3D device.
            winrt::com_ptr<::ID3D11Device> d3dDevice;
            D3D_FEATURE_LEVEL supportedFeatureLevel;
            winrt::check_hresult(::D3D11CreateDevice(
                nullptr,
                D3D_DRIVER_TYPE_HARDWARE,
                0,
                creationFlags,
                featureLevels,
                ARRAYSIZE(featureLevels),
                D3D11_SDK_VERSION,
                d3dDevice.put(),
                &supportedFeatureLevel,
                nullptr)
            );
    
            // Get the Direct3D device.
            winrt::com_ptr<::IDXGIDevice> dxgiDevice{
                d3dDevice.as<::IDXGIDevice>() };
    
            // Create the Direct2D device and a corresponding context.
            winrt::com_ptr<::ID2D1Device> d2dDevice;
            ::D2D1CreateDevice(dxgiDevice.get(), nullptr, d2dDevice.put());
    
            winrt::com_ptr<::ID2D1DeviceContext> d2dDeviceContext;
            winrt::check_hresult(
                d2dDevice->CreateDeviceContext(
                    D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
                    d2dDeviceContext.put()
                )
            );
        }
    }
    
  4. Encapsule sua marcação XAML em um elemento SwapChainPanel com um x:Name. Os elementos XAML encapsulados serão renderizados na frente do SwapChainPanel.

    <!-- MainPage.xaml -->
     <SwapChainPanel x:Name="swapChainPanel">
     	<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
     		<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
     	</StackPanel>
     </SwapChainPanel>
    

    Em seguida, você pode acessar esse objeto SwapChainPanel por meio da função de acessador com o mesmo nome, como veremos.

  5. Em seguida, chame IDXGIFactory2::CreateSwapChainForComposition para criar uma cadeia de troca.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    // Get the DXGI adapter.
    winrt::com_ptr< ::IDXGIAdapter > dxgiAdapter;
    dxgiDevice->GetAdapter(dxgiAdapter.put());
    
    // Get the DXGI factory.
    winrt::com_ptr< ::IDXGIFactory2 > dxgiFactory;
    dxgiFactory.capture(dxgiAdapter, &IDXGIAdapter::GetParent);
    
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc { 0 };
    swapChainDesc.Width = 500;
    swapChainDesc.Height = 500;
    swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swapchain format.
    swapChainDesc.Stereo = false;
    swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.BufferCount = 2;
    swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // We recommend using this swap effect for all applications.
    swapChainDesc.Flags = 0;
    
    // Create a swap chain by calling IDXGIFactory2::CreateSwapChainForComposition.
    winrt::com_ptr< ::IDXGISwapChain1 > swapChain;
    dxgiFactory->CreateSwapChainForComposition(
        d3dDevice.get(),
        &swapChainDesc,
        nullptr,
        swapChain.put());
    
  6. Obtenha um ISwapChainPanelNative do SwapChainPanel que você nomeou swapChainPanel. A chamada ISwapChainPanelNative::SetSwapChain para definir a cadeia de troca no SwapChainPanel.

    // MainPage.cpp | paste this at the end of MainPage::ClickHandler
    // Get native interface for SwapChainPanel
    auto panelNative{ swapChainPanel().as<ISwapChainPanelNative>() };
    
    winrt::check_hresult(
        panelNative->SetSwapChain(swapChain.get())
    );
    
  7. Por fim, desenhe para a cadeia de troca do DirectX e, em seguida, apresente-a para exibir o conteúdo.

    // Create a Direct2D target bitmap associated with the
    // swap chain back buffer, and set it as the current target.
    D2D1_BITMAP_PROPERTIES1 bitmapProperties =
        D2D1::BitmapProperties1(
            D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
            D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
            96.f,
            96.f
        );
    
    winrt::com_ptr<::IDXGISurface> dxgiBackBuffer;
    swapChain->GetBuffer(0, __uuidof(dxgiBackBuffer), dxgiBackBuffer.put_void());
    
    winrt::com_ptr< ::ID2D1Bitmap1 > targetBitmap;
    winrt::check_hresult(
        d2dDeviceContext->CreateBitmapFromDxgiSurface(
            dxgiBackBuffer.get(),
            &bitmapProperties,
            targetBitmap.put()
        )
    );
    
    d2dDeviceContext->SetTarget(targetBitmap.get());
    
    // Draw using Direct2D context.
    d2dDeviceContext->BeginDraw();
    
    d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Orange));
    
    winrt::com_ptr<::ID2D1SolidColorBrush> brush;
    winrt::check_hresult(d2dDeviceContext->CreateSolidColorBrush(
        D2D1::ColorF(D2D1::ColorF::Chocolate),
        D2D1::BrushProperties(0.8f),
        brush.put()));
    
    D2D1_SIZE_F const size{ 500, 500 };
    D2D1_RECT_F const rect{ 100.0f, 100.0f, size.width - 100.0f, size.height - 100.0f };
    d2dDeviceContext->DrawRectangle(rect, brush.get(), 100.0f);
    
    d2dDeviceContext->EndDraw();
    
    swapChain->Present(1, 0);
    

    Os elementos XAML são atualizados quando o Tempo de execução Windows organiza/renderiza sinais lógicos de uma atualização.

  8. Agora você pode criar e executar o aplicativo. Clique no botão para ver o conteúdo do SwapChainPanel exibido atrás dos outros elementos XAML.

    Um retângulo renderizado Direct2D atrás de um elemento de botão XAML

Observação

Em geral, seu aplicativo DirectX deve criar cadeias de troca na orientação paisagem e igual ao tamanho da janela de exibição (que geralmente é a resolução de tela nativa na maioria dos jogos da Microsoft Store). Isso garante que seu aplicativo use a implementação de cadeia de troca ideal quando ele não tiver nenhuma sobreposição XAML visível. Se o aplicativo for girado para o modo retrato, seu aplicativo deverá chamar IDXGISwapChain1::SetRotation na cadeia de troca existente, aplicar uma transformação ao conteúdo, se necessário, e chamar SetSwapChain novamente na mesma cadeia de troca. Da mesma forma, seu aplicativo deverá chamar SetSwapChain novamente na mesma cadeia de troca sempre que esta for redimensionada com uma chamada para IDXGISwapChain::ResizeBuffers.