DirectX and XAML in Windows (Phone) 8: RT Component and C# Integration
Join the DZone community and get the full member experience.
Join For FreeIn the previous post I have created a basic Windows 8 application featuring both DirectX and XAML. However, the sample was written in C++/CX and there aren’t any project templates for writing applications with both XAML and DirectX since the latter one is exposed only in C++. Luckily, there is a way for you to write C++ code that deals with DirectX which you could then reference in C#/VB – by creating WinRT component.
Creating an WinRT component
You can create Windows Runtime components in either C++ or your favorite managed language. Start Visual Studio and create new project with Visual C++/Windows Store/Windows Runtime Component. Delete the sample class created by the template (seen on the image below).
Add two files to the project and name them d2dcomponent.h
and d2dcomponent.cpp
. You write WinRT components by writing a simple C++/CX class. Note that in order to be consumable from the outside world, it must be a part of the namespace. Putting the class in the global namespace yields errors. Here is the basic layout for our component:
namespace _2_sis_component { public ref class D2DComponent sealed : Windows::UI::Xaml::Media::Imaging::SurfaceImageSource { public: D2DComponent(int pixelWidth, int pixelHeight, bool isOpaque); }; }
Notice that our component inherits SurfaceImageSource. This makes it a XAML component and that means it is really easy to consume it from C#. Also note that I have added a non-default constructor to the class since SurfaceImageSource needs three parameters in its constructor. It is relatively easy to implement the constructor with the following code:
D2DComponent::D2DComponent(int pixelWidth, int pixelHeight, bool isOpaque) : SurfaceImageSource(pixelWidth, pixelHeight, isOpaque) {}
Referencing the component in managed application
Now create a blank C# Windows store app and add WinRT component as project reference via the Solution tab on the Add Reference dialog.
Like the last time, we need to add XAML element that will use our DirectX surface and we need to bind it in the code behind. Add a Rectangle XAML element to the MainPage
and name it “rectangle”. Now open MainPage.xaml.cs and add the following code in the OnNavigatedTo
function:
var imageBrush = new ImageBrush(); var _d2dComponent = new _2_sis_component.D2DComponent(300, 300, true); imageBrush.ImageSource = _d2dComponent; rectangle.Fill = imageBrush;
It was that easy to create a Windows Runtime Component in C++ and consume it in C#. The actual plumbing is done automatically and you do not need to worry about it. It is now time to add the actual code to the component that will use DirectX. For that we can use the code from the previous post, only this time we will refactor it into a class.
Completing d2dcomponent
First add necessary includes to pch.h
:
#include <D3D11.h> #include <d2d1_1.h> #include <wrl.h> #include <windows.ui.xaml.media.dxinterop.h>
Don’t forget to link the necessary libraries also.
Now let’s complete our d2dcomponent class. We need to add the necessary fields for DirectX related objects and a few extras for rendering. The class should look something like this:
public ref class D2DComponent sealed : Windows::UI::Xaml::Media::Imaging::SurfaceImageSource { private protected: Microsoft::WRL::ComPtr<ISurfaceImageSourceNative> _sisNative; Microsoft::WRL::ComPtr<ID3D11Device> _d3dDevice; Microsoft::WRL::ComPtr<ID3D11DeviceContext> _d3dContext; Microsoft::WRL::ComPtr<IDXGIDevice> _dxgiDevice; Microsoft::WRL::ComPtr<ID2D1Device> _d2dDevice; Microsoft::WRL::ComPtr<ID2D1DeviceContext> _d2dDeviceContext; int _height; int _width; float _dpi; void CreateDeviceIndependentResources(); void CreateDeviceResources(); public: D2DComponent(int pixelWidth, int pixelHeight, bool isOpaque); void BeginDraw(Windows::Foundation::Rect updateRect); void BeginDraw() { BeginDraw(Windows::Foundation::Rect(0, 0, (float)_width, (float)_height)); } void EndDraw(); void FillRectangle(int x, int y, int width, int height, Windows::UI::Color color); void SetDpi(float dpi); void Clear(Windows::UI::Color color); };
That is a lot to swallow, but it looks similar to the code from the last post. Notice that FillRectangle
and Clear
methods have parameter with type Windows::UI::Color
. Most of the functions in Direct2D use D2D1::ColorF
for specifying color and you can use this handy function to perform conversion:
D2D1::ColorF ToNativeColor(Windows::UI::Color color) { return D2D1::ColorF(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f); }
I suggest that you check the rest of the implementation for the class on github. Let’s focus for a moment on the FillRectangle
method. This method is supposed to be called from other WinRT components or Windows Store applications. It exposes drawing a filled rectangle using a method that is available in DirectX API, but visible to the managed world. It is easy to implement it and hides the complexity of DirectX for us. Here is the full code for the function:
void D2DComponent::FillRectangle(int x, int y, int width, int height, Windows::UI::Color color) { Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush; _d2dDeviceContext->CreateSolidColorBrush(ToNativeColor(color), &brush); _d2dDeviceContext->FillRectangle(D2D1::RectF(x, y, x + width, y + height), brush.Get()); }
Let’s modify our C# code now. Instead of exposing a single method from the component to handle the entire drawing, we have exposed methods such as BeginDraw
, EndDraw
, Clear
andFillRectangle
. Let’s draw with C#:
_d2dComponent.BeginDraw(); _d2dComponent.Clear(Windows.UI.Colors.Bisque); var random = new Random(); for (int i = 0; i < 20; ++i) { _d2dComponent.FillRectangle( random.Next(0, 250), random.Next(0, 250), 50, 50, Windows.UI.Color.FromArgb( (byte)255, (byte)random.Next(0, 255), (byte)random.Next(0, 255), (byte)random.Next(0, 255))); } _d2dComponent.EndDraw();
Yay, everything is rendered via C#. Here is the final result:
Referencing WinRT component in another native Windows 8 app
You can reference and use WinRT in native apps just as easily. Solution references are added via theReferences… context menu action on the C++ project. Once you add it, the rest is relatively straightforward. Here is the rendering code written in C++/CX:
auto imageBrush = ref new ImageBrush(); auto _d2dComponent = ref new _2_sis_component::D2DComponent(300, 300, true); imageBrush->ImageSource = _d2dComponent; rectangle->Fill = imageBrush; _d2dComponent->BeginDraw(); _d2dComponent->Clear(Windows::UI::Colors::Bisque); for (int i = 0; i < 20; ++i) { Windows::UI::Color color; color.R = rand() % 255 + 1; color.G = rand() % 255 + 1; color.B = rand() % 255 + 1; _d2dComponent->FillRectangle( rand() % 251, rand() % 251, 50, 50, color); } _d2dComponent->EndDraw();
The differences are subtle and language dependent, but you can see how easy is to consume WinRT components from native app.
Conclusion
It is easy to create WinRT components in C++ that use DirectX and expose them in C#. However, we face one significant drawback: we cannot pass DirectX objects from C++/CX layer to our C# app. Although we can write wrapper methods like FillRectangle, it is obvious that this is not the way to go forward. But before we do that, I will first explore what Windows Phone 8 brought to the table.
As usually, all code can be found on github.
Published at DZone with permission of Toni Petrina, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Working on an Unfamiliar Codebase
-
What Is mTLS? How To Implement It With Istio
-
How To Design Reliable IIoT Architecture
-
Security Challenges for Microservice Applications in Multi-Cloud Environments
Comments