Interoperabilidade entre DirectX e XAML

Observação

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

Você pode usar XAML (Extensible Application Markup Language) junto com o Microsoft DirectX juntos em seu jogo ou aplicativo UWP (Plataforma Universal do Windows). A combinação de XAML e DirectX permite que você crie estruturas de interface do usuário flexíveis que interoperam com o conteúdo renderizado em DirectX; o 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 o seu aplicativo se concentrar principalmente na renderização 2D, talvez você queira usar a biblioteca Win2D do Tempo de Execução do Windows. Essa biblioteca é mantida pela Microsoft e é criada com base na tecnologia Direct2D principal. 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 do projeto para obter mais detalhes. Este documento aborda diretrizes para desenvolvedores de aplicativos que optam por não usar o Win2D.

Observação

As APIs do DirectX não são definidas como tipos do Tempo de Execução do Windows, mas você pode usar o C++/WinRT para desenvolver aplicativos UWP XAML que interoperam com o DirectX. Se você fatorar o código que chama o DirectX em seu próprio componente do Tempo de Execução do Windows (WRC) C++/WinRT, poderá usar esse WRC em um aplicativo UWP (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 a gráficos mais complexos. Para isso, você pode usar Direct2D e Direct3D para renderizar os gráficos mais complexos e usar XAML para elementos de interface do usuário (interface do usuário) mais tradicionais.

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

  • Superfícies compartilhadas são regiões dimensionadas da exibição, definidas por XAML, que você pode usar o DirectX para desenhar indiretamente usando 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 da superfície compartilhada são sincronizadas com as atualizações da estrutura XAML.
  • Uma cadeia de troca representa uma coleção de buffers usados para exibir gráficos com latência mínima. Normalmente, uma cadeia de troca é atualizada a 60 quadros por segundo separadamente do thread da interface do usuário. No entanto, uma cadeia de troca usa mais memória e recursos de 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 ajuste às dimensões da janela de exibição? Ele conterá 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ê deve ficar bem usando uma superfície compartilhada.

Depois de determinar como pretende usar o DirectX, use um dos seguintes tipos de Tempo de Execução do Windows 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 em uma superfície compartilhada com Windows::UI::Xaml::Media::Imaging::SurfaceImageSource. Esse tipo lida com uma superfície de desenho do DirectX dimensionada. Normalmente, você usa esse tipo ao compor uma imagem ou textura como um bitmap para exibição em um documento ou elemento de interface do usuário. Não funciona bem para interatividade em tempo real, como um jogo de alto desempenho. Isso ocorre porque as atualizações de um objeto SurfaceImageSource são sincronizadas com as atualizações da interface do usuário XAML e isso pode introduzir latência nos comentários visuais que você fornece ao usuário, como uma taxa de quadros flutuante ou uma resposta ruim percebida à entrada em tempo real. No entanto, as atualizações ainda são rápidas o suficiente para controles dinâmicos ou simulações de dados.
  • Se a imagem for maior do que o espaço de tela fornecido e puder ser movida ou ampliada pelo usuário, use Windows::UI::Xaml::Media::Imaging::VirtualSurfaceImageSource. Esse tipo lida com uma superfície de desenho do DirectX dimensionada que é maior que a tela. Como SurfaceImageSource, você usa isso ao compor uma imagem complexa ou controle dinamicamente. E, também como o SurfaceImageSource, 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 grande e denso em imagens.
  • 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 a cadeia de troca do dispositivo gráfico (IDXGISwapChain1) diretamente e colocar XAML sobre o destino de renderização. SwapChainPanel funciona muito bem para jogos e aplicativos DirectX de 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 obter mais informações, consulte Guia de programação do Direct3D 11.

FonteImagem de superfície

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

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 na superfície DXGI com Direct2D (ou Direct3D).
  • Termine de desenhar 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 mergulho mais profundo 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). 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 ao topo de MainPage.cpp, abaixo das que já estão lá. Também em 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 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 seu SurfaceImageSource de um thread em segundo plano, também precisará garantir que o dispositivo DXGI tenha acesso multithread habilitado (conforme mostrado no código abaixo). Por motivos de desempenho, você deve fazer isso somente 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 multithread. 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 de 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) atualmente gera uma exceção. Não o chame do objeto SurfaceImageSource.

    Observação

    Evite desenhar em SurfaceImageSource enquanto sua janela estiver oculta ou inativa, caso contrário , as APIs ISurfaceImageSourceNativeWithD2D falharão. Lide com 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 no 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 retangular espesso e laranja escuro contra um fundo laranja mais claro

VirtualSurfaceImageSource

VirtualSurfaceImageSource estende SurfaceImageSource e é para cenários em que o conteúdo é potencialmente muito grande 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 maneira ideal. Por exemplo, aplicativos de mapeamento ou telas de documentos grandes.

Dica

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

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

É uma boa ideia se familiarizar com SurfaceImageSource (consulte a seção SurfaceImageSource acima) antes de lidar com 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.
  • No 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 mergulho mais profundo 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 VSISDemo (é importante dar esse nome ao projeto se você for copiar e colar 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 precisa ser atualizada, a estrutura chama 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 movimento panorâmico ou amplia a exibição da superfície, por exemplo) ou depois que seu aplicativo chama IVirtualSurfaceImageSourceNative::Invalidate nessa região.

    Em sua 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 o desenho às regiões delimitadas para obter melhor desempenho. As especificidades de chamar os métodos ISurfaceImageSourceNativeWithD2D são as mesmas de SurfaceImageSource (consulte a seção SurfaceImageSource acima).

    Observação

    Evite desenhar em VirtualSurfaceImageSource enquanto sua janela estiver oculta ou inativa, caso contrário , as APIs ISurfaceImageSourceNativeWithD2D falharão. Lide com 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) nele chamando ISurfaceImageSourceNativeWithD2D::SetDevice.

    Observação

    Se você estiver desenhando para o VirtualSurfaceImageSource de um thread em segundo plano, também precisará garantir que o dispositivo DXGI tenha o acesso multithread habilitado (conforme mostrado no código abaixo). Por motivos de desempenho, você deve fazer isso somente 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 no 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 do Tempo de Execução do Windows projetado para dar suporte a gráficos e jogos de alto desempenho, em que você gerencia a cadeia de troca diretamente. Nesse caso, você cria sua própria cadeia de troca do DirectX e gerencia a apresentação do conteúdo renderizado. Outro recurso do SwapChainPanel é que você pode sobrepor outros elementos XAML na frente dele.

Dica

Os seguintes aplicativos de exemplo demonstram SurfaceImageSource: renderização avançada de imagem colorida Direct2D, ajuste de foto Direct2D, renderização de imagem SVG Direct2D, entrada de baixa latência, jogo DirectX e XAML e interoperabilidade XAML SwapChainPanel DirectX (Windows 8.1).

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 permuta do DirectX (em DXGI_SWAP_CHAIN_DESC1) para as dimensões atuais do elemento da cadeia de permuta. Caso contrário, 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.

Você atualiza o SwapChainPanel com base nas necessidades do seu aplicativo e não sincronizado com as atualizações da estrutura XAML. Se você precisar sincronizar as atualizações do SwapChainPanel com as da estrutura XAML, registre-se para o evento Windows::UI::Xaml::Media::CompositionTarget::Rendering. Caso contrário, você deve considerar quaisquer problemas entre threads se tentar atualizar os elementos XAML de um thread diferente daquele que atualiza o 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 normais de entrada de ponteiro XAML não serão gerados para o SwapChainPanel, pois todas as entradas serão redirecionadas para o thread em segundo plano.

Aqui está 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 SCPDemo (é importante dar esse nome ao projeto se você for copiar e colar 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 acessadora com o mesmo nome, como veremos.

  5. Em seguida, chame a chamada 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 a cadeia de troca do DirectX e 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 a lógica de layout/renderização do Tempo de Execução do Windows sinaliza 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 em Direct2D por trá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 ideal da cadeia de troca quando 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 deve chamar SetSwapChain novamente na mesma cadeia de troca sempre que a cadeia de troca for redimensionada chamando IDXGISwapChain::ResizeBuffers.