DirectX- und XAML-Interoperabilität
Hinweis
Dieses Thema bezieht sich auf Universelle Windows-Plattform (UWP)-Spiele und -Apps sowie auf Typen in den Windows.UI.Xaml.Xxx Namespaces (nicht Microsoft.UI.Xaml.Xxx).
Sie können Extensible Application Markup Language (XAML) zusammen mit Microsoft DirectX zusammen in Ihrem Universelle Windows-Plattform(UWP)-Spiel oder Ihrer App verwenden. Mit der Kombination aus XAML und DirectX können Sie flexible Benutzeroberflächenframeworks erstellen, die mit Ihren directX-gerenderten Inhalten zusammenarbeiten. dies ist besonders nützlich für grafikintensive Apps. In diesem Thema wird die Struktur einer UWP-App erläutert, die DirectX verwendet, und die wichtigen Typen identifiziert, die beim Erstellen Ihrer UWP-App für die Arbeit mit DirectX verwendet werden sollen.
Wenn sich Ihre App hauptsächlich auf das 2D-Rendering konzentriert, sollten Sie die Win2D-Windows-Runtime-Bibliothek verwenden. Diese Bibliothek wird von Microsoft verwaltet und basiert auf der zentralen Direct2D-Technologie . Win2D vereinfacht das Verwendungsmuster zum Implementieren von 2D-Grafiken erheblich und enthält hilfreiche Abstraktionen für einige der in diesem Dokument beschriebenen Techniken. Weitere Informationen finden Sie auf der Projektseite. Dieses Dokument enthält Anleitungen für App-Entwickler, die nicht Win2D verwenden möchten.
Hinweis
DirectX-APIs sind nicht als Windows-Runtime Typen definiert, Sie können jedoch C++/WinRT verwenden, um XAML-UWP-Apps zu entwickeln, die mit DirectX zusammenarbeiten. Wenn Sie den Code, der DirectX in eine eigene C++/WinRT-Windows-Runtime-Komponente (WRC) aufruft, einteilen, können Sie diesen WRC in einer UWP-App (auch in einer C#-App) verwenden, die xaml und DirectX kombiniert.
XAML und DirectX
DirectX bietet zwei leistungsstarke Bibliotheken für 2D- und 3D-Grafiken – Direct2D und Direct3D. Obwohl XAML Unterstützung für grundlegende 2D-Grundtypen und -effekte bietet, benötigen viele Modellierungs- und Spiele-Apps komplexere Grafikunterstützung. Für diese können Sie Direct2D und Direct3D verwenden, um die komplexeren Grafiken zu rendern und XAML für herkömmlichere Benutzeroberflächenelemente zu verwenden.
Wenn Sie benutzerdefinierte XAML- und DirectX-Interoperabilität implementieren, müssen Sie diese beiden Konzepte kennen.
- Freigegebene Oberflächen sind Größenbereiche der Anzeige, die durch XAML definiert werden, und Sie können DirectX verwenden, um indirekt mithilfe von Windows::UI::Xaml::Media::ImageSource-Typen zu zeichnen. Bei freigegebenen Oberflächen steuern Sie nicht die genaue Anzeigedauer, wann neue Inhalte auf dem Bildschirm angezeigt werden. Stattdessen werden Aktualisierungen der freigegebenen Oberfläche mit den Updates des XAML-Frameworks synchronisiert.
- Eine Swapchain stellt eine Auflistung von Puffern dar, die zum Anzeigen von Grafiken mit minimaler Latenz verwendet werden. In der Regel wird eine Swapchain separat vom UI-Thread mit 60 Frames pro Sekunde aktualisiert. Eine Swapchain verwendet jedoch mehr Arbeitsspeicher und CPU-Ressourcen, um schnelle Updates zu unterstützen und ist relativ schwierig zu verwenden, da Sie mehrere Threads verwalten müssen.
Überlegen Sie, wofür Sie DirectX verwenden. Verwenden Sie es, um ein einzelnes Steuerelement zu erstellen oder zu animieren, das in die Abmessungen des Anzeigefensters passt? Enthält sie Ausgabe, die wie in einem Spiel in Echtzeit gerendert und gesteuert werden muss? Wenn ja, müssen Sie wahrscheinlich eine Swapchain implementieren. Andernfalls sollten Sie eine gemeinsam genutzte Oberfläche verwenden.
Nachdem Sie festgelegt haben, wie Sie DirectX verwenden möchten, verwenden Sie einen der folgenden Windows-Runtime Typen, um DirectX-Rendering in Ihre UWP-App zu integrieren.
- Wenn Sie ein statisches Bild erstellen oder ein komplexes Bild in ereignisgesteuerten Intervallen zeichnen möchten, zeichnen Sie auf eine freigegebene Oberfläche mit Windows::UI::Xaml::Media::Imaging::SurfaceImageSource. Dieser Typ behandelt eine DirectX-Zeichnungsoberfläche mit einer Größe. In der Regel verwenden Sie diesen Typ beim Verfassen eines Bilds oder einer Textur als Bitmap für die Anzeige in einem Dokument oder UI-Element. Es funktioniert nicht gut für Interaktivität in Echtzeit, z. B. für ein Hochleistungsspiel. Das liegt daran, dass Aktualisierungen eines SurfaceImageSource-Objekts mit XAML-Benutzeroberflächenupdates synchronisiert werden und die Latenz in das visuelle Feedback, das Sie dem Benutzer bereitstellen, wie z. B. eine schwankende Bildrate oder eine wahrgenommene schlechte Antwort auf Echtzeiteingaben, ermöglichen. Updates sind jedoch immer noch schnell genug für dynamische Steuerelemente oder Datensimulationen.
- Wenn das Bild größer als die bereitgestellte Bildschirmfläche ist und vom Benutzer verschoben oder vergrößert werden kann, verwenden Sie Windows::UI::Xaml::Media::Imaging::VirtualSurfaceImageSource. Dieser Typ behandelt eine DirectX-Zeichnungsoberfläche, die größer als der Bildschirm ist. Wie SurfaceImageSource verwenden Sie dies beim dynamischen Verfassen eines komplexen Bilds oder Steuerelements. Und auch wie SurfaceImageSource funktioniert es nicht gut für Hochleistungsspiele. Einige Beispiele für XAML-Elemente, die eine VirtualSurfaceImageSource verwenden können, sind Kartensteuerelemente oder eine große, bilddichte Dokumentanzeige.
- Wenn Sie DirectX zum Präsentieren von Grafiken verwenden, die in Echtzeit aktualisiert wurden, oder in einer Situation, in der die Updates in regelmäßigen Intervallen mit geringer Latenz auftreten müssen, verwenden Sie die SwapChainPanel-Klasse , damit Sie die Grafiken aktualisieren können, ohne mit dem Aktualisierungszeitgeber des XAML-Frameworks zu synchronisieren. Mit SwapChainPanel können Sie direkt auf die Swapchain des Grafikgeräts (IDXGISwapChain1) zugreifen und XAML über das Renderziel auf layern. SwapChainPanel eignet sich hervorragend für Spiele und DirectX-Vollbild-Apps, die eine XAML-basierte Benutzeroberfläche erfordern. Sie müssen DirectX gut kennen, um diesen Ansatz zu verwenden, einschließlich der Microsoft DirectX Graphics Infrastructure (DXGI), Direct2D- und Direct3D-Technologien. Weitere Informationen finden Sie im Programmierhandbuch für Direct3D 11.
SurfaceImageSource
SurfaceImageSource bietet Ihnen eine gemeinsam genutzte DirectX-Oberfläche zum Zeichnen. Anschließend werden die Bits in App-Inhalte erstellt.
Tipp
Die Beispielanwendungen für den Zeilenabstand (DirectWrite) und die herunterladbaren Schriftarten (DirectWrite) veranschaulichen SurfaceImageSource.
Hier ist der Prozess zum Erstellen und Aktualisieren einer SurfaceImageSource auf sehr hoher Ebene.
- Erstellen Sie ein Direct 3D-Gerät, ein Direct 2D-Gerät und einen Direct 2D-Gerätekontext.
- Erstellen Sie eine SurfaceImageSource, und legen Sie dafür das Direct 2D-Gerät (oder Direct 3D) fest.
- Beginnen Sie mit der Zeichnung auf surfaceImageSource, um eine DXGI-Oberfläche zu erhalten.
- Zeichnen Sie mit Direct2D (oder Direct3D) auf die DXGI-Oberfläche.
- Beenden Sie die Zeichnung auf surfaceImageSource, wenn Sie fertig sind.
- Legen Sie die SurfaceImageSource auf einem XAML-Bild oder ImageBrush fest, um sie in Ihrer XAML-Benutzeroberfläche anzuzeigen.
Und hier finden Sie einen tieferen Einblick in diese Schritte mit Quellcodebeispielen.
Sie können den unten gezeigten und beschriebenen Code befolgen, indem Sie ein neues Projekt in Microsoft Visual Studio erstellen. Erstellen Sie ein leeres App-Projekt (C++/WinRT). Die neueste allgemein verfügbare Version von Windows SDK (d. h. keine Vorschauversion).
Öffnen Sie
pch.h
, und fügen Sie Folgendes hinzu, einschließlich unter den bereits dort aufgeführten.// 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>
Fügen Sie die
using
unten gezeigte Direktive am oberen Rand vonMainPage.cpp
, unter den bereits dort aufgeführten hinzu. Ersetzen Sie auch inMainPage.cpp
, die vorhandene Implementierung von MainPage::ClickHandler durch die unten gezeigte Auflistung. Der Code erstellt ein Direct 3D-Gerät, ein Direct 2D-Gerät und einen Direct 2D-Gerätekontext. Dazu wird D3D11CreateDevice, D2D1CreateDevice und ID2D1Device::CreateDeviceContext aufgerufen.// 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() ) ); }
Fügen Sie als Nächstes Code zum Erstellen einer SurfaceImageSource hinzu, und legen Sie das Direct 2D-Gerät (oder Direct 3D) darauf fest, indem Sie ISurfaceImageSourceNativeWithD2D::SetDevice aufrufen.
Hinweis
Wenn Sie von einem Hintergrundthread aus auf SurfaceImageSource zeichnen, müssen Sie auch sicherstellen, dass das DXGI-Gerät multithreadbasierten Zugriff aktiviert hat (wie im folgenden Code dargestellt). Aus Leistungsgründen sollten Sie dies nur tun, wenn Sie aus einem Hintergrundthread zeichnen.
Definieren Sie die Größe der freigegebenen Oberfläche, indem Sie die Höhe und Breite an den SurfaceImageSource-Konstruktor übergeben. Sie können auch angeben, ob die Oberfläche Alphaunterstützung (Deckkraft) benötigt.
Um das Gerät festzulegen und die Draw-Vorgänge auszuführen, benötigen wir einen Zeiger auf ISurfaceImageSourceNativeWithD2D. Um ein Objekt abzurufen, fragen Sie das SurfaceImageSource-Objekt nach der zugrunde liegenden ISurfaceImageSourceNativeWithD2D-Schnittstelle ab.
// 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);
Rufen Sie ISurfaceImageSourceNativeWithD2D::BeginDraw auf, um eine DXGI-Oberfläche (eine IDXGISurface-Schnittstelle) abzurufen. Sie können ISurfaceImageSourceNativeWithD2D::BeginDraw (und die späteren Zeichnungsbefehle) aus einem Hintergrundthread aufrufen, wenn Sie den Multithreadzugriff aktiviert haben. In diesem Schritt erstellen Sie auch eine Bitmap aus der DXGI-Oberfläche, und legen Sie diese im Direct 2D-Gerätekontext fest.
Im Offsetparameter gibt ISurfaceImageSourceNativeWithD2D::BeginDraw den Punktoffset (ein x,y-Wert) des aktualisierten Zielrechtecks zurück. Sie können diesen Offset verwenden, um zu bestimmen, wo Der aktualisierte Inhalt mit dem ID2D1DeviceContext gezeichnet werden soll.
// 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());
Verwenden Sie den Direct 2D-Gerätekontext, um den Inhalt der SurfaceImageSource zu zeichnen. Es wird nur der bereich gezeichnet, der im vorherigen Schritt im UpdateRect-Parameter für die Aktualisierung angegeben ist.
// 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(); }
Rufen Sie ISurfaceImageSourceNativeWithD2D::EndDraw auf, um die Bitmap abzuschließen (Sie müssen ISurfaceImageSourceNativeWithD2D::EndDraw nur über den UI-Thread aufrufen). Legen Sie dann surfaceImageSource auf einem XAML-Bild (oder ImageBrush) fest, um sie in Der XAML-Benutzeroberfläche anzuzeigen.
// 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);
Hinweis
Das Aufrufen von SurfaceImageSource::SetSource (geerbt von IBitmapSource::SetSource) löst derzeit eine Ausnahme aus. Rufen Sie sie nicht von Ihrem SurfaceImageSource-Objekt auf.
Hinweis
Vermeiden Sie das Zeichnen auf SurfaceImageSource, während Ihr Fenster ausgeblendet oder inaktiv ist, andernfalls schlägt ISurfaceImageSourceNativeWithD2D-APIs fehl. Behandeln Sie Ereignisse rund um die Fenstersichtbarkeit und das Anhalten der Anwendung, um dies zu erreichen.
Fügen Sie schließlich das folgende Image-Element innerhalb des vorhandenen XAML-Markups hinzu.
MainPage.xaml
<!-- MainPage.xaml --> ... <Image x:Name="theImage" Width="500" Height="500" /> ...
Jetzt können Sie die App erstellen und ausführen. Klicken Sie auf die Schaltfläche, um den Inhalt der im Bild angezeigten SurfaceImageSource anzuzeigen.
VirtualSurfaceImageSource
VirtualSurfaceImageSource erweitert SurfaceImageSource, und es ist für Szenarien gedacht, in denen der Inhalt potenziell zu groß ist, um gleichzeitig auf den Bildschirm zu passen (und/oder zu groß, um den Videospeicher als einzelne Textur einzupassen), und daher muss der Inhalt virtualisiert werden, um optimal zu rendern. Beispiel: Zuordnen von Apps oder großen Dokumentbereichs.
Tipp
Die Komplexe Freihandbeispielanwendung veranschaulicht VirtualSurfaceImageSource.
VirtualSurfaceImageSource unterscheidet sich von SurfaceImageSource darin, dass ein Rückruf verwendet wird – IVirtualSurfaceImageSourceCallbacksNative::UpdatesNeeded –, das Sie implementieren, um Bereiche der Oberfläche zu aktualisieren, sobald sie auf dem Bildschirm sichtbar werden. Sie müssen keine ausgeblendeten Bereiche löschen, da das XAML-Framework dies für Sie übernimmt.
Es ist ratsam, sich mit SurfaceImageSource vertraut zu machen (siehe den Abschnitt SurfaceImageSource oben), bevor Sie VirtualSurfaceImageSource angehen. Aber auf sehr hoher Ebene ist hier der Prozess zum Erstellen und Aktualisieren einer VirtualSurfaceImageSource.
- Implementieren Sie die IVirtualSurfaceImageSourceCallbackNative-Schnittstelle .
- Erstellen Sie ein Direct 3D-Gerät, ein Direct 2D-Gerät und einen Direct 2D-Gerätekontext.
- Erstellen Sie eine VirtualSurfaceImageSource, und legen Sie das Direct 2D-Gerät (oder Direct 3D) auf diesem Gerät fest.
- Call RegisterForUpdatesNeededed on the VirtualSurfaceImageSource.
- Rufen Sie in Ihrem UpdatesNeeded-Rückruf GetUpdateRectCount und GetUpdateRects auf.
- Rendern Sie die Updaterechtecke (mit BeginDraw EndDraw/ genau wie für SurfaceImageSource).
- Legen Sie die SurfaceImageSource auf einem XAML-Bild oder ImageBrush fest, um sie in Ihrer XAML-Benutzeroberfläche anzuzeigen.
Und hier finden Sie einen tieferen Einblick in diese Schritte mit Quellcodebeispielen.
Sie können den unten gezeigten und beschriebenen Code befolgen, indem Sie ein neues Projekt in Microsoft Visual Studio erstellen. Erstellen Sie ein Leeres App-Projekt (C++/WinRT), und nennen Sie es VSISDemo (es ist wichtig, dem Projekt diesen Namen zu geben, wenn Sie in den unten angegebenen Codeauflistungen kopieren werden). Die neueste allgemein verfügbare Version von Windows SDK (d. h. keine Vorschauversion).
Öffnen Sie
pch.h
, und fügen Sie Folgendes hinzu, einschließlich unter den bereits dort aufgeführten.// 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>
In diesem Schritt stellen Sie eine Implementierung der IVirtualSurfaceUpdatesCallbackNative-Schnittstelle bereit. Fügen Sie dem Projekt ein neues Headerdateielement (H) hinzu, und nennen Sie es CallbackImplementation.h. Ersetzen Sie den Inhalt dieser Datei durch die nachstehende Auflistung. Der Code wird nach der Auflistung erläutert.
#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 }; }; }
Immer wenn eine Region der VirtualSurfaceImageSource aktualisiert werden muss, ruft das Framework Ihre Implementierung von IVirtualSurfaceUpdatesCallbackNative::UpdatesNeeded (siehe oben) auf.
Dies kann passieren, wenn das Framework bestimmt, dass der Bereich gezeichnet werden muss (wenn der Benutzer die Ansicht der Oberfläche verschiebt oder vergrößert, z. B. nach dem Aufruf der App "IVirtualSurfaceImageSourceNative::Invalidate " in diesem Bereich).
Verwenden Sie in Ihrer Implementierung von IVirtualSurfaceImageSourceNative::UpdatesNeeded die Methoden IVirtualSurfaceImageSourceNative::GetUpdateRectCount und IVirtualSurfaceImageSourceNative::GetUpdateRects , um zu bestimmen, welche Bereiche der Oberfläche gezeichnet werden müssen.
Zeichnen Sie für jede Region, die aktualisiert werden muss, den spezifischen Inhalt in diesen Bereich, beschränken Sie die Zeichnung jedoch auf die begrenzten Bereiche, um eine bessere Leistung zu erzielen. Die Besonderheiten des Aufrufens der ISurfaceImageSourceNativeWithD2D-Methoden sind identisch mit SurfaceImageSource (siehe den Abschnitt "SurfaceImageSource").
Hinweis
Vermeiden Sie das Zeichnen auf VirtualSurfaceImageSource, während Ihr Fenster ausgeblendet oder inaktiv ist, andernfalls schlägt ISurfaceImageSourceNativeWithD2D-APIs fehl. Behandeln Sie Ereignisse rund um die Fenstersichtbarkeit und das Anhalten der Anwendung, um dies zu erreichen.
In der MainPage-Klasse fügen wir ein Element vom Typ CallbackImplementation hinzu. Außerdem erstellen wir ein Direct 3D-Gerät, ein Direct 2D-Gerät und einen Direct 2D-Gerätekontext. Dazu rufen wir D3D11CreateDevice, D2D1CreateDevice und ID2D1Device::CreateDeviceContext auf.
Ersetzen Sie den Inhalt von
MainPage.idl
,MainPage.h
undMainPage.cpp
durch den Inhalt der nachfolgenden Auflistungen.// 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() ) ); } }
Fügen Sie als Nächstes Code hinzu, um eine VirtualSurfaceImageSource mit der gewünschten Größe zu erstellen, und legen Sie das Direct 2D-Gerät (oder Direct 3D) darauf fest, indem Sie ISurfaceImageSourceNativeWithD2D::SetDevice aufrufen.
Hinweis
Wenn Sie von einem Hintergrundthread aus auf Ihre VirtualSurfaceImageSource zeichnen, müssen Sie auch sicherstellen, dass das DXGI-Gerät multithreadbasierten Zugriff aktiviert hat (wie im folgenden Code dargestellt). Aus Leistungsgründen sollten Sie dies nur tun, wenn Sie aus einem Hintergrundthread zeichnen.
Um das Gerät festzulegen und die Draw-Vorgänge auszuführen, benötigen wir einen Zeiger auf ISurfaceImageSourceNativeWithD2D. Um ein Objekt abzurufen, fragen Sie das VirtualSurfaceImageSource-Objekt nach der zugrunde liegenden ISurfaceImageSourceNativeWithD2D-Schnittstelle ab.
Fragen Sie außerdem IVirtualSurfaceImageSourceNative ab, und rufen Sie IVirtualSurfaceImageSourceNative::RegisterForUpdatesNeeded auf, und stellen Sie Ihre Implementierung von IVirtualSurfaceUpdatesCallbackNative bereit.
Legen Sie dann surfaceImageSource auf einem XAML-Bild (oder ImageBrush) fest, um sie in Der XAML-Benutzeroberfläche anzuzeigen.
// 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);
Fügen Sie schließlich das folgende Image-Element innerhalb des vorhandenen XAML-Markups hinzu.
MainPage.xaml
<!-- MainPage.xaml --> ... <Image x:Name="theImage" Width="500" Height="500" /> ...
Jetzt können Sie die App erstellen und ausführen. Klicken Sie auf die Schaltfläche, um den Inhalt der im Bild angezeigten VirtualSurfaceImageSource anzuzeigen.
SwapChainPanel und Spiele
SwapChainPanel ist der Windows-Runtime Typ, der entwickelt wurde, um hochleistungsfähige Grafiken und Spiele zu unterstützen, bei denen Sie die Swapchain direkt verwalten. In diesem Fall erstellen Sie Ihre eigene DirectX-Swapchain und verwalten die Darstellung Ihrer gerenderten Inhalte. Ein weiteres Feature von SwapChainPanel besteht darin, dass Sie andere XAML-Elemente davor überlagern können.
Tipp
Die folgenden Beispielanwendungen veranschaulichen SurfaceImageSource: Direct2D advanced color image rendering, Direct2D photo adjustment, Direct2D SVG image rendering, Low latency input, DirectX and XAML game, and XAML SwapChainPanel DirectX interop (Windows 8.1).
Um eine gute Leistung sicherzustellen, gibt es bestimmte Einschränkungen für den SwapChainPanel-Typ .
- Pro App sollten maximal vier SwapChainPanel-Instanzen vorhanden sein.
- Sie sollten die Höhe und Breite der DirectX-Swapchain (in DXGI_SWAP_CHAIN_DESC1) auf die aktuellen Dimensionen des Swapchainelements festlegen. Wenn dies nicht der Derb ist, wird der Anzeigeinhalt entsprechend skaliert (mit DXGI_SCALING_STRETCH).
- Sie müssen den Skalierungsmodus der DirectX-Swapchain (in DXGI_SWAP_CHAIN_DESC1) auf DXGI_SCALING_STRETCH festlegen.
- Sie müssen die DirectX-Swapchain erstellen, indem Sie IDXGIFactory2::CreateSwapChainForComposition aufrufen.
Sie aktualisieren swapChainPanel basierend auf den Anforderungen Ihrer App und synchronisieren nicht mit den Updates des XAML-Frameworks. Wenn Sie die Updates des SwapChainPanel mit denen des XAML-Frameworks synchronisieren müssen, registrieren Sie sich für das Windows::UI::Xaml::Media::CompositionTarget::Rendering-Ereignis. Andernfalls müssen Sie alle threadübergreifenden Probleme berücksichtigen, wenn Sie versuchen, die XAML-Elemente aus einem anderen Thread zu aktualisieren als das, das SwapChainPanel aktualisiert.
Wenn Sie Zeigereingaben mit geringer Latenz auf SwapChainPanel empfangen müssen, verwenden Sie SwapChainPanel::CreateCoreIndependentInputSource. Diese Methode gibt ein CoreIndependentInputSource -Objekt zurück, das verwendet werden kann, um Eingabeereignisse mit minimaler Latenz in einem Hintergrundthread zu empfangen. Beachten Sie, dass nach dem Aufruf dieser Methode keine normalen XAML-Zeigereingabeereignisse für swapChainPanel ausgelöst werden, da alle Eingaben an den Hintergrundthread umgeleitet werden.
Hier sehen Sie den Prozess zum Erstellen und Aktualisieren eines SwapChainPanel-Objekts.
Sie können den unten gezeigten und beschriebenen Code befolgen, indem Sie ein neues Projekt in Microsoft Visual Studio erstellen. Erstellen Sie ein leeres App-Projekt (C++/WinRT), und nennen Sie es SCPDemo (es ist wichtig, dem Projekt diesen Namen zu geben, wenn Sie in den unten angegebenen Codeauflistungen kopieren werden). Die neueste allgemein verfügbare Version von Windows SDK (d. h. keine Vorschauversion).
Öffnen Sie
pch.h
, und fügen Sie Folgendes hinzu, einschließlich unter den bereits dort aufgeführten.// pch.h ... #include <d3d11_4.h> #include <d2d1_1.h> #include <windows.ui.xaml.media.dxinterop.h>
In der MainPage-Klasse erstellen wir zunächst ein Direct 3D-Gerät, ein Direct 2D-Gerät und einen Direct 2D-Gerätekontext. Dazu rufen wir D3D11CreateDevice, D2D1CreateDevice und ID2D1Device::CreateDeviceContext auf.
Ersetzen Sie den Inhalt von
MainPage.idl
,MainPage.h
undMainPage.cpp
durch den Inhalt der nachfolgenden Auflistungen.// 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() ) ); } }
Schließen Sie das XAML-Markup in ein SwapChainPanel-Element mit einem
x:Name
. Die umschlossenen XAML-Elemente werden vor swapChainPanel gerendert.<!-- MainPage.xaml --> <SwapChainPanel x:Name="swapChainPanel"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> <Button x:Name="myButton" Click="ClickHandler">Click Me</Button> </StackPanel> </SwapChainPanel>
Sie können dann über die Accessorfunktion mit demselben Namen auf dieses SwapChainPanel-Objekt zugreifen, wie wir sehen.
Rufen Sie als Nächstes IDXGIFactory2::CreateSwapChainForComposition auf, um eine Swapchain zu erstellen.
// 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());
Rufen Sie eine ISwapChainPanelNative aus dem SwapChainPanel-Element ab, das Sie swapChainPanel genannt haben. Der Aufruf von "ISwapChainPanelNative::SetSwapChainChain" zum Festlegen der SwapchainPanel-Swapchain.The call ISwapChainPanelNative::SetSwapChain to set the 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()) );
Zeichnen Sie schließlich in die DirectX-Swapchain, und zeigen Sie sie dann an, um den Inhalt anzuzeigen.
// 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);
Die XAML-Elemente werden aktualisiert, wenn die Windows-Runtime Layout-/Renderlogik eine Aktualisierung signalisiert.
Jetzt können Sie die App erstellen und ausführen. Klicken Sie auf die Schaltfläche, um den Inhalt des SwapChainPanel anzuzeigen, der hinter den anderen XAML-Elementen angezeigt wird.
Hinweis
Im Allgemeinen sollte Ihre DirectX-App Swapchains im Querformat erstellen und gleich der Anzeigefenstergröße (normalerweise die systemeigene Bildschirmauflösung in den meisten Microsoft Store-Spielen) erstellen. Dadurch wird sichergestellt, dass Ihre App die optimale Swapchainimplementierung verwendet, wenn keine sichtbaren XAML-Überlagerungen vorhanden sind. Wenn die App in den Hochformatmodus gedreht wird, sollte Ihre App IDXGISwapChain1::SetRotation in der vorhandenen Swapchain aufrufen, bei Bedarf eine Transformation auf den Inhalt anwenden und dann SetSwapChain erneut in derselben Swapchain aufrufen. Ebenso sollte Ihre App SetSwapChain erneut in derselben Swapchain aufrufen, wenn die Swapchain durch Aufrufen von IDXGISwapChain::ResizeBuffers geändert wird.