Взаимодействие DirectX и XAML

Примечание

Этот раздел относится к универсальная платформа Windows играм и приложениям UWP, а также к типам в Windows.UI.Xaml.Xxx пространствах имен (не Microsoft.UI.Xaml.Xxx).

Вы можете использовать XAML вместе с Microsoft DirectX в игре или приложении универсальная платформа Windows (UWP). Сочетание XAML и DirectX позволяет создавать гибкие платформы пользовательского интерфейса, которые взаимодействуют с отображаемым содержимым DirectX; что особенно полезно для приложений с интенсивным использованием графики. В этом разделе объясняется структура приложения UWP, использующего DirectX, и определены важные типы, которые следует использовать при создании приложения UWP для работы с DirectX.

Если ваше приложение в основном ориентировано на двухd-отрисовку, может потребоваться использовать библиотеку среда выполнения Windows Win2D. Эта библиотека поддерживается корпорацией Майкрософт и основана на базовой технологии Direct2D . Win2D значительно упрощает шаблон использования для реализации двухD-графики и включает полезные абстракции для некоторых методов, описанных в этом документе. Для получения дополнительных сведений см. страницу проекта. В этом документе приводится руководство для разработчиков приложений, которые не используют Win2D.

Примечание

API DirectX не определены как среда выполнения Windows типы, но вы можете использовать C++/WinRT для разработки приложений XAML UWP, взаимодействующих с DirectX. Если вы разместите код, вызывающий DirectX, в собственный компонент C++/WinRT среда выполнения Windows (WRC), вы можете использовать этот WRC в приложении UWP (даже в C#), которое затем объединяет XAML и DirectX.

XAML и DirectX

DirectX предоставляет две мощные библиотеки для двухмерной и трехмерной графики соответственно— Direct2D и Direct3D. Хотя XAML обеспечивает поддержку базовых двухдиалоговых примитивов и эффектов, многие приложения моделирования и игровые приложения нуждаются в более сложной поддержке графики. Для этого можно использовать Direct2D и Direct3D для отрисовки более сложной графики, а xaml — для более традиционных элементов пользовательского интерфейса.

Если вы реализуете пользовательское взаимодействие XAML и DirectX, необходимо знать эти два понятия.

  • Общие поверхности — это области отображения размера, определенные XAML, которые можно использовать DirectX для косвенного рисования с помощью типов Windows::UI::Xaml::Media::ImageSource . Для общих поверхностей вы не контролируете точное время появления нового содержимого на экране. Вместо этого обновления общей поверхности синхронизируются с обновлениями платформы XAML.
  • Цепочка буферов представляет собой коллекцию буферов, которые используются для отображения графики с минимальной задержкой. Как правило, цепочка буферов обновляется со скоростью 60 кадров в секунду отдельно от потока пользовательского интерфейса. Однако цепочка буферов использует больше памяти и ресурсов ЦП для поддержки быстрых обновлений, и ее относительно трудно использовать, так как вам нужно управлять несколькими потоками.

Рассмотрим, для чего вы используете DirectX. Будете ли вы использовать его для композитирования или анимации одного элемента управления, который помещается в размеры окна отображения? Будет ли DirectX содержать выходные данные, требующие обработки и управления в режиме реального времени, как, например, в игре? Если это так, то вам, вероятно, потребуется реализовать цепочку буферов. В противном случае следует использовать общую поверхность.

Определив способ использования DirectX, используйте один из следующих типов среда выполнения Windows для включения отрисовки DirectX в приложение UWP.

  • Если вы хотите создать статическое изображение или нарисовать сложное изображение с интервалами, управляемыми событиями, выполните рисование на общей поверхности с помощью Windows::UI::Xaml::Media::Imaging::SurfaceImageSource. Этот тип обрабатывает поверхность рисования DirectX размером. Обычно данный тип используется при формировании изображения или текстуры, например точечного рисунка, для отображения в документе или элементе пользовательского интерфейса. Он плохо подходит для обеспечения взаимодействия в реальном времени, например в высокопроизводительных играх. Это связано с тем, что обновления объекта SurfaceImageSource синхронизируются с обновлениями пользовательского интерфейса XAML, и это может привести к задержке в визуальной обратной связи, которую вы предоставляете пользователю, например колебания частоты кадров или восприятие плохой реакции на входные данные в режиме реального времени. Обновления по-прежнему достаточно быстры для динамических элементов управления или моделирования данных.
  • Если изображение больше предоставленного пространства экрана и может быть развернуто или увеличено пользователем, используйте Windows::UI::Xaml:::Media::Imaging::VirtualSurfaceImageSource. Этот тип обрабатывает размер поверхности рисования DirectX, которая больше, чем экран. Как и SurfaceImageSource, вы используете его при динамическом создании сложного изображения или элемента управления. Кроме того, как и класс SurfaceImageSource, он плохо подходит для высокопроизводительных игр. К примерам элементов XAML, в которых может использоваться класс VirtualSurfaceImageSource, относятся элементы управления картой или средство просмотра документов с большим количеством изображений.
  • Если вы используете DirectX для представления обновленной графики в режиме реального времени или в ситуации, когда обновления должны поступать через обычные интервалы с низкой задержкой, используйте класс SwapChainPanel , чтобы можно было обновить графику без синхронизации с таймером обновления платформы XAML. С помощью SwapChainPanel вы можете напрямую получить доступ к цепочке буферов графического устройства (IDXGISwapChain1) и выполнить слой XAML поверх целевого объекта отрисовки. SwapChainPanel отлично подходит для игр и полноэкранных приложений DirectX, которым требуется пользовательский интерфейс на основе XAML. Чтобы использовать этот подход, вы должны хорошо знать DirectX, включая технологии Microsoft DirectX Graphics Infrastructure (DXGI), Direct2D и Direct3D. Дополнительные сведения см. в Руководстве по программированию для Direct3D 11.

SurfaceImageSource

SurfaceImageSource предоставляет общую поверхность DirectX для рисования; затем он создает биты в содержимое приложения.

Совет

Примеры приложений Междустрочный интервал (DirectWrite) и Загружаемые шрифты (DirectWrite) демонстрируют surfaceImageSource.

На очень высоком уровне вот процесс создания и обновления SurfaceImageSource.

  • Создание трехмерного устройства Direct, двухd-устройства Direct и контекста 2D-устройства Direct.
  • Создайте SurfaceImageSource и настройте на этом устройстве Direct 2D (или Direct 3D).
  • Начните рисование на SurfaceImageSource , чтобы получить поверхность DXGI.
  • Рисование на поверхности DXGI с помощью Direct2D (или Direct3D).
  • Завершив рисование на SurfaceImageSource , завершите работу.
  • Задайте SurfaceImageSource в xaml Image или ImageBrush , чтобы отобразить его в пользовательском интерфейсе XAML.

Ниже приведены более подробные сведения об этих шагах с примерами исходного кода.

  1. Чтобы выполнить приведенный ниже код, создайте новый проект в Microsoft Visual Studio. Создайте проект пустого приложения (C++/WinRT). В качестве цели выберите последнюю общедоступную (то есть не предварительную) версию Windows SDK.

  2. Откройте pch.hи добавьте следующие элементы под уже имеющимися.

    // 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. Добавьте директиву using , показанную ниже, в верхнюю часть MainPage.cpp, под теми, которые уже есть. Кроме того, в MainPage.cppзамените существующую реализацию MainPage::ClickHandler приведенным ниже описанием. Код создает трехмерное устройство Direct, двухd-устройство Direct и контекст двухD-устройств Direct. Для этого он вызывает D3D11CreateDevice, D2D1CreateDevice и 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. Затем добавьте код для создания SurfaceImageSource и задайте устройство Direct 2D (или Direct 3D) на этом устройстве, вызвав метод ISurfaceImageSourceNativeWithD2D::SetDevice.

    Примечание

    Если вы будете выполнять рисование в SurfaceImageSource из фонового потока, необходимо также убедиться, что на устройстве DXGI включен многопотоковый доступ (как показано в коде ниже). По соображениям производительности это следует делать только в том случае, если вы будете рисовать из фонового потока.

    Определите размер общей поверхности, передав высоту и ширину конструктору SurfaceImageSource. Вы можете также указать, требуется ли поверхности поддержка альфа-режима (непрозрачности).

    Чтобы задать устройство и выполнить операции рисования, нам потребуется указатель на ISurfaceImageSourceNativeWithD2D. Чтобы получить его, запросите у объекта SurfaceImageSource базовый интерфейс ISurfaceImageSourceNativeWithD2D .

    // 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. Вызовите метод ISurfaceImageSourceNativeWithD2D::BeginDraw , чтобы получить поверхность DXGI (интерфейс IDXGISurface ). Вы можете вызвать ISurfaceImageSourceNativeWithD2D::BeginDraw (и последующие команды рисования) из фонового потока, если вы включили многопотоковый доступ. На этом шаге вы также создадите растровое изображение из поверхности DXGI и зададите его в контексте 2D-устройства Direct.

    В параметре offsetISurfaceImageSourceNativeWithD2D::BeginDraw возвращает смещение точки (значение x,y) обновленного целевого прямоугольника. Это смещение можно использовать для определения места рисования обновленного содержимого с помощью 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. Используйте контекст двухмерного устройства Direct для рисования содержимого SurfaceImageSource. Рисуется только область, указанная для обновления на предыдущем шаге в параметре updateRect .

    // 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. Вызовите метод ISurfaceImageSourceNativeWithD2D::EndDraw , чтобы завершить растровое изображение (необходимо вызывать ISurfaceImageSourceNativeWithD2D::EndDraw только из потока пользовательского интерфейса). Затем задайте SurfaceImageSource для изображения XAML (или ImageBrush), чтобы отобразить его в пользовательском интерфейсе 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);
    

    Примечание

    Вызов SurfaceImageSource::SetSource (наследуемого от IBitmapSource::SetSource) в настоящее время вызывает исключение. Не вызывайте его из объекта SurfaceImageSource .

    Примечание

    Избегайте рисования в SurfaceImageSource , пока окно скрыто или неактивно, в противном случае api ISurfaceImageSourceNativeWithD2D завершится ошибкой. Для этого обработайте события, связанные с видимостью окна и приостановкой приложения.

  8. Наконец, добавьте следующий элемент Image в существующую разметку XAML в MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  9. Теперь можно выполнить сборку и запуск приложения. Нажмите кнопку, чтобы просмотреть содержимое SurfaceImageSource , отображаемое на изображении.

    Толстый, темно-оранжевый прямоугольный контур на светло-оранжевом фоне

VirtualSurfaceImageSource

VirtualSurfaceImageSource расширяет возможности SurfaceImageSource и предназначен для сценариев, когда содержимое может быть слишком большим, чтобы поместиться на экране одновременно (или слишком большое, чтобы поместиться в видеопамяти в виде одной текстуры), поэтому содержимое должно быть виртуализировано для оптимальной отрисовки. Например, приложения для сопоставления или большие холсты документов.

Совет

Пример приложения для сложного рукописного ввода демонстрирует VirtualSurfaceImageSource.

VirtualSurfaceImageSource отличается от SurfaceImageSource тем, что использует обратный вызов IVirtualSurfaceImageSourceCallbacksNative::UpdatesNeeded, который реализуется для обновления областей поверхности по мере их отображения на экране. Вам не нужно очищать скрытые регионы, так как платформа XAML позаботится об этом за вас.

Прежде чем работать с VirtualSurfaceImageSource, рекомендуется ознакомиться с SurfaceImageSource (см. раздел SurfaceImageSource выше). Но на очень высоком уровне вот процесс создания и обновления VirtualSurfaceImageSource.

  • Реализуйте интерфейс IVirtualSurfaceImageSourceCallbackNative .
  • Создание трехмерного устройства Direct, двухd-устройства Direct и контекста 2D-устройства Direct.
  • Создайте VirtualSurfaceImageSource и настройте на этом устройстве Direct 2D (или Direct 3D).
  • Вызовите RegisterForUpdatesNeeded вVirtualSurfaceImageSource.
  • В обратном вызове UpdatesNeeded вызовите GetUpdateRectCount и GetUpdateRects.
  • Отрисовка прямоугольников обновления (с помощью BeginDraw/EndDraw , как и для SurfaceImageSource).
  • Задайте SurfaceImageSource в xaml Image или ImageBrush , чтобы отобразить его в пользовательском интерфейсе XAML.

Ниже приведены более подробные сведения об этих шагах с примерами исходного кода.

  1. Чтобы выполнить приведенный ниже код, создайте новый проект в Microsoft Visual Studio. Создайте проект пустого приложения (C++/WinRT) и назовите его VSISDemo (важно присвоить проекту это имя, если вы будете выполнять копирование и вставку в приведенных ниже списках кода). В качестве цели выберите последнюю общедоступную (то есть не предварительную) версию Windows SDK.

  2. Откройте pch.hи добавьте следующие элементы под уже имеющимися.

    // 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. На этом шаге вы предоставите реализацию интерфейса IVirtualSurfaceUpdatesCallbackNative . Добавьте в проект новый элемент файла заголовков (.h) и назовите его CallbackImplementation.h. Замените содержимое этого файла приведенным ниже описанием. Код описывается после перечисления.

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

    Всякий раз, когда требуется обновить область VirtualSurfaceImageSource , платформа вызывает реализацию IVirtualSurfaceUpdatesCallbackNative::UpdatesNeeded (как показано выше).

    Это может произойти, когда платформа определяет, что регион необходимо нарисовать (например, когда пользователь сдвигает или масштабирует представление поверхности), или после того, как приложение вызовет IVirtualSurfaceImageSourceNative::Invalidate в этом регионе.

    В реализации IVirtualSurfaceImageSourceNative::UpdatesNeeded используйте методы IVirtualSurfaceImageSourceNative::GetUpdateRectCount и IVirtualSurfaceImageSourceNative::GetUpdateRects , чтобы определить, какие области поверхности должны быть отрисованы.

    Для каждого региона, который необходимо обновить, нарисуйте определенное содержимое в этом регионе, но ограничьте документ ограниченными регионами для повышения производительности. Особенности вызова методов ISurfaceImageSourceNativeWithD2D такие же, как и для SurfaceImageSource (см. раздел SurfaceImageSource выше).

    Примечание

    Избегайте рисования в VirtualSurfaceImageSource , когда окно скрыто или неактивно, в противном случае API ISurfaceImageSourceNativeWithD2D завершится ошибкой. Для этого обработайте события, связанные с видимостью окна и приостановкой приложения.

  4. В классе MainPage мы добавим член типа CallbackImplementation. Мы также создадим трехмерное устройство Direct, двухD-устройство Direct и контекст двухD-устройств Direct. Для этого мы вызовем D3D11CreateDevice, D2D1CreateDevice и ID2D1Device::CreateDeviceContext.

    Замените содержимое MainPage.idl, MainPage.hи MainPage.cpp на содержимое приведенных ниже списков.

    // 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. Затем добавьте код для создания VirtualSurfaceImageSource с нужным размером и задайте для этого устройства Direct 2D (или Direct 3D), вызвав метод ISurfaceImageSourceNativeWithD2D::SetDevice.

    Примечание

    Если вы будете выполнять рисование в VirtualSurfaceImageSource из фонового потока, необходимо также убедиться, что на устройстве DXGI включен многопотоковый доступ (как показано в коде ниже). По соображениям производительности это следует делать только в том случае, если вы будете рисовать из фонового потока.

    Чтобы задать устройство и выполнить операции рисования, нам потребуется указатель на ISurfaceImageSourceNativeWithD2D. Чтобы получить его, запросите объект VirtualSurfaceImageSource для его базового интерфейса ISurfaceImageSourceNativeWithD2D .

    Кроме того, запросите IVirtualSurfaceImageSourceNative и вызовите IVirtualSurfaceImageSourceNative::RegisterForUpdatesNeeded, предоставляя реализацию IVirtualSurfaceUpdatesCallbackNative.

    Затем задайте SurfaceImageSource для изображения XAML (или ImageBrush), чтобы отобразить его в пользовательском интерфейсе 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. Наконец, добавьте следующий элемент Image в существующую разметку XAML в MainPage.xaml.

    <!-- MainPage.xaml -->
    ...
    <Image x:Name="theImage" Width="500" Height="500" />
    ...
    
  7. Теперь можно выполнить сборку и запуск приложения. Нажмите кнопку, чтобы просмотреть содержимое объекта VirtualSurfaceImageSource , отображаемого на изображении.

SwapChainPanel и игры

SwapChainPanel — это тип среды выполнения Windows для поддержки высокопроизводительных игр и графики, в котором вы управляете цепочкой буферов напрямую. В данном случае вы создаете собственную цепочку буферов DirectX и управляете представлением своего отображаемого содержимого. Еще одна особенность SwapChainPanel заключается в том, что перед ней можно накладывать другие элементы XAML.

Чтобы обеспечить высокую производительность, существуют определенные ограничения для типа SwapChainPanel .

  • На каждое приложение должно быть не более 4 экземпляров SwapChainPanel .
  • Необходимо задать для цепочки буферов DirectX высоту и ширину (в DXGI_SWAP_CHAIN_DESC1) текущие измерения элемента цепочки буферов. В противном случае содержимое отображения будет масштабироваться в соответствии с DXGI_SCALING_STRETCH.
  • Необходимо задать режим масштабирования цепочки буферов DirectX (в DXGI_SWAP_CHAIN_DESC1) на DXGI_SCALING_STRETCH.
  • Необходимо создать цепочку буферов DirectX путем вызова IDXGIFactory2::CreateSwapChainForComposition.

Вы обновляете SwapChainPanel в соответствии с потребностями приложения и не синхронизируете его с обновлениями платформы XAML. Если необходимо синхронизировать обновления SwapChainPanel с обновлениями платформы XAML, зарегистрируйтесь для события Windows::UI::Xaml::Media::CompositionTarget::Rendering . В противном случае необходимо учитывать все возможные проблемы с разными потоками при попытке обновления элементов XAML не из того потока, который обновляет SwapChainPanel.

Если необходимо получить входные данные указателя с низкой задержкой на SwapChainPanel, используйте SwapChainPanel::CreateCoreIndependentInputSource. Этот метод возвращает объект CoreIndependentInputSource , который можно использовать для получения входных событий с минимальной задержкой в фоновом потоке. Обратите внимание, что после вызова этого метода обычные входные события указателя XAML не будут вызываться для SwapChainPanel, так как все входные данные будут перенаправлены в фоновый поток.

Ниже описан процесс создания и обновления объекта SwapChainPanel .

  1. Чтобы выполнить приведенный ниже код, создайте новый проект в Microsoft Visual Studio. Создайте проект пустого приложения (C++/WinRT) и назовите его SCPDemo (важно присвоить проекту это имя, если будет выполняться копирование и вставка в приведенных ниже списках кода). В качестве цели выберите последнюю общедоступную (то есть не предварительную) версию Windows SDK.

  2. Откройте pch.hи добавьте следующие элементы под уже имеющимися.

    // pch.h
    ...
    #include <d3d11_4.h>
    #include <d2d1_1.h>
    #include <windows.ui.xaml.media.dxinterop.h>
    
  3. В классе MainPage мы сначала создадим трехмерное устройство Direct, двухD-устройство Direct и контекст двухD-устройств Direct. Для этого мы вызовем D3D11CreateDevice, D2D1CreateDevice и ID2D1Device::CreateDeviceContext.

    Замените содержимое MainPage.idl, MainPage.hи MainPage.cpp на содержимое приведенных ниже списков.

    // 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. Заключите разметку XAML в элемент SwapChainPanel с помощью x:Name. Заключенные в оболочку элементы XAML отрисовываются перед 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>
    

    Затем вы можете получить доступ к объекту SwapChainPanel с помощью функции доступа с тем же именем, как мы увидим.

  5. Затем вызовите IDXGIFactory2::CreateSwapChainForComposition , чтобы создать цепочку буферов.

    // 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. Получите ISwapChainPanelNative из SwapChainPanel , который вы назвали swapChainPanel. Вызов ISwapChainPanelNative::SetSwapChain для задания цепочки буферов в 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. Наконец, нарисуйте цепочку буферов DirectX, а затем откройте ее, чтобы отобразить содержимое.

    // 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);
    

    Элементы XAML обновляются, когда логика макета или отрисовки среды выполнения Windows сигнализирует об обновлении.

  8. Теперь можно выполнить сборку и запуск приложения. Нажмите кнопку, чтобы просмотреть содержимое SwapChainPanel , отображаемое за другими элементами XAML.

    Прямоугольник, отрисованный Direct2D за элементом кнопки XAML

Примечание

Как правило, приложение DirectX должно создавать цепочки буферов в альбомной ориентации и равняться размеру окна отображения (обычно это собственное разрешение экрана в большинстве игр Из Microsoft Store). Это гарантирует, что приложение использует оптимальную реализацию цепочки буферов, если у него нет видимого наложения XAML. Если приложение повернется в книжный режим, приложение должно вызвать IDXGISwapChain1::SetRotation в существующей цепочке буферов, при необходимости применить преобразование к содержимому, а затем снова вызвать SetSwapChain в той же цепочке буферов. Схожим образом ваше приложение должно снова вызывать SetSwapChain в той же цепочке буферов, когда размер этой цепочки изменяется посредством вызова IDXGISwapChain::ResizeBuffers.