Mobile Zone is brought to you in partnership with:

My name is Toni Petrina and I am a software developer and an occasional speaker. Although I primarily develop on the Microsoft stack, I like to learn new technologies. My hobbyist projects range from game development, regardless of the technology, to ALM. I spend most of my time with my girlfriend and someday I will learn how to play the guitar properly. Toni is a DZone MVB and is not an employee of DZone and has posted 69 posts at DZone. You can read more from them at their website. View Full User Profile

DirectX and XAML in Windows (Phone) 8: RT Component and C# Integration

11.14.2012
| 4438 views |
  • submit to reddit

In 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 FillRectangleand 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 BeginDrawEndDrawClear 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, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)