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

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

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

Примечание

API DirectX не определены как типы среда выполнения Windows, но вы можете использовать C++/WinRT для разработки приложений UWP XAML, взаимодействующих с 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 3D, устройства Direct 2D и контекста устройства Direct 2D.
  • Создайте SurfaceImageSource и задайте на этом устройстве Direct 2D (или Direct 3D).
  • Начните рисование на SurfaceImageSource , чтобы получить поверхность DXGI.
  • Рисование на поверхности DXGI с помощью Direct2D (или Direct3D).
  • Завершив рисование на SurfaceImageSource .
  • Задайте SurfaceImageSource на изображении XAML или 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 3D, устройство Direct 2D и контекст устройства Direct 2D. Для этого он вызывает 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 и установите его в контексте устройства Direct 2D.

    В параметре смещенияISurfaceImageSourceNativeWithD2D::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 2D для рисования содержимого 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 , отображаемое на изображении.

    A thick, dark orange rectanglular outline against a lighter orange background

VirtualSurfaceImageSource

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

Совет

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

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

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

  • Реализуйте интерфейс IVirtualSurfaceImageSourceCallbackNative .
  • Создание устройства Direct 3D, устройства Direct 2D и контекста устройства Direct 2D.
  • Создайте VirtualSurfaceImageSource и задайте на этом устройстве Direct 2D (или Direct 3D).
  • Вызов RegisterForUpdatesNeeded в VirtualSurfaceImageSource.
  • В обратном вызове UpdatesNeeded вызовите GetUpdateRectCount и GetUpdateRects.
  • Отрисовка прямоугольников обновления (с помощью BeginDrawEndDraw/ так же, как для SurfaceImageSource).
  • Задайте SurfaceImageSource на изображении XAML или 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 3D, устройство Direct 2D и контекст устройства Direct 2D. Для этого мы будем вызывать D3D11CreateDevice, D2D1CreateDevice и ID2D1Device::CreateDeviceContext.

    Замените содержимое MainPage.idlи MainPage.hMainPage.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.

Совет

В следующих примерах приложений демонстрируется surfaceImageSource: расширенная отрисовка изображений direct2D, настройка фотографий Direct2D, отрисовка изображений SVG Direct2D, ввод с низкой задержкой, игра DirectX и XAMLSwapChainPanel DirectX (Windows 8.1).

Чтобы обеспечить высокую производительность, существуют определенные ограничения для типа 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 3D, устройство Direct 2D и контекст устройства Direct 2D. Для этого мы будем вызывать D3D11CreateDevice, D2D1CreateDevice и ID2D1Device::CreateDeviceContext.

    Замените содержимое MainPage.idlи MainPage.hMainPage.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.

    A Direct2D-rendered rectangle behind a XAML button element

Примечание

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