Поделиться через


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

Примечание.

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

Вы можете использовать расширяемый язык разметки приложений (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 обеспечивает поддержку основных примитивов и эффектов 2D, многие приложения моделирования и игр нуждаются в более сложной графической поддержке. Для этого можно использовать Direct2D и Direct3D для отрисовки более сложных графических элементов и использовать XAML для более традиционных элементов пользовательского интерфейса.

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

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

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

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

  • Если вы хотите создать статическое изображение или нарисовать сложное изображение через интервалы, управляемые событиями, нарисуйте общую поверхность в Windows::UI::Xaml::Media::Imageing::SurfaceImageSource. Этот тип обрабатывает область рисования DirectX размером. Как правило, этот тип используется при создании изображения или текстуры в качестве растрового изображения для отображения в документе или элементе пользовательского интерфейса. Это не работает хорошо для интерактивного взаимодействия в режиме реального времени, например высокопроизводительной игры. Это связано с тем, что обновления объекта SurfaceImageSource синхронизируются с обновлениями пользовательского интерфейса XAML, и что может привести к задержке в визуальном отзыве, предоставленном пользователю, например колебания частоты кадров или предполагаемого плохого ответа на входные данные в режиме реального времени. Обновления по-прежнему достаточно быстры для динамических элементов управления или моделирования данных, однако.
  • Если изображение больше предоставленного пространства экрана и может быть сдвигаться или увеличиваться пользователем, и используйте Windows::UI::Xaml::Media::Imageing::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, отображаемое на изображении.

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

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.
  • Отрисовка прямоугольников обновления (с помощью BeginDraw EndDraw/ так же, как для 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.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.

Совет

В следующих примерах приложений демонстрируется SurfaceImageSource: расширенная отрисовка изображений Direct2D, настройка фотографий Direct2D SVG, низкая задержка ввода, игра DirectX и XAML SwapChainPanel 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.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.