Tutorial Series - Introduction to Direct3D with XAML application development for Windows Phone 8

Introduction

Welcome to this tutorial about Direct3D for Windows Phone 8! As part of this tutorial, I decided to start with an empty Windows Phone project instead using default Visual Studio templates to build Direct3D application. We are going to add the plumbing to figure out, step by step, how communication between C# and C++ components is performed, and then how 3D assets can be created and manipulated by using Direct3D.

At the end of this tutorial series, you will be able to display the Omega Crusher space ship designed by Michel with textures and lightning (as displayed in the video). For this first post, I will start by creating the structure of the solution and the code to initialize 3D surface with a beautiful midnight blue. By the way, lot of plumbing today, the fun part with 3D will come in futures posts :).

 

Few words about Direct3D templates provided by Visual Studio

I will start with an empty project to explain how components interacts with each other. Anyway, if you start a new development, you can of course use one of these templates to save time. For reference, Windows Phone SDK 8.0 provides three native templates to create Direct3D applications.

Windows Phone Direct3D App (Native Only)

This template is located in Visual C++ folder, this is a pure native application that does not support XAML. If your app needs standard controls, like text boxes, buttons, or check boxes, you will need to write these from scratch or utilize third-party libraries.

Windows Phone Direct3D with XAML App

This template is located in Visual C++ folder too but provides a structure with two components, one managed with C#, the other one in pure native C++. The managed part is based on a DrawingSurfaceBackgroundGrid control which allows you to use Direct3D to render graphics that are displayed across the entire background of your app; the Direct3D code for rendering graphics is implemented in a separate Windows Phone Runtime component (the C++ part).

This is the project structure that I will implement into this tutorial today.

Windows Phone XAML and Direct3D App

This template is located in Visual C# or Visual Basic folders. The project structure is quite similar to the previous one, however the XAML interface host a DrawingSurface control instead DrawingSurfaceBackgroundGrid.

DrawingSurface control can be placed anywhere in the screen (as any standard XAML control) unlike DrawingSurfaceBackgroundGrid which must be placed at the root of the XAML tree and always covers the entire screen. The frame rate of an application using DrawingSurfaceBackgroundGrid control is slightly better than DrawingSurface application.

Building the structure of the solution

1. Create a new Windows Phone App project named OmegaCrusher as described below.

1

2. Change the MainPage.xaml layout to place a DrawingSurfaceBackgroundGrid control at the root of the Visual Tree.

The graphics you draw using this control cover the phone’s entire screen and are displayed behind any other XAML controls you use on your page. To draw Direct3D graphics on a portion of the screen that is sized and arranged just like any other XAML control, see DrawingSurface.

MainPage.xaml

 <phone:PhoneApplicationPage
    x:Class="OmegaCrusher.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!-- DrawingSurfaceBackgroundGrid -->
    <DrawingSurfaceBackgroundGrid x:Name="DrawingSurfaceBackground" Loaded="DrawingSurfaceBackground_Loaded">
    </DrawingSurfaceBackgroundGrid>

</phone:PhoneApplicationPage>

3. Clean the code behind to remove comments and add the DrawingSurfaceBackgroundGrid_Loaded event handler .

MainPage.xaml.cs

 using System;
using System.Windows;
using Microsoft.Phone.Controls;
using OmegaCrusher.Resources;

namespace OmegaCrusher
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructor
        public MainPage()
        {
            InitializeComponent();
        }

        private void DrawingSurfaceBackground_Loaded(object sender, RoutedEventArgs e)
        {

        }
    }
}

4. Add a new Windows Phone Runtime Component from Visual C++ templates named OmegaCrusher.PhoneComponent.

2

5. Rename WindowsPhoneRuntimeComponent into Direct3DBackground to obtain the following header and code files.

OmegaCrusher.PhoneComponent.h

 #pragma once

namespace OmegaCrusher_PhoneComponent
{
    public ref class Direct3DBackground sealed
    {
    public:
        Direct3DBackground();
    };
}

OmegaCrusher.PhoneComponent.cpp

 // OmegaCrusher.PhoneComponent.cpp
#include "pch.h"
#include "OmegaCrusher.PhoneComponent.h"

using namespace OmegaCrusher_PhoneComponent;
using namespace Platform;

Direct3DBackground::Direct3DBackground()
{
}

6. Add a reference from OmegaCrusher project to include OmegaCrusher.PhoneComponent component.

image

7. Then update MainPage.xaml.cs to instanciate Direct3DBackground Component as declared in code below.

 using System;
using System.Windows;
using OmegaCrusher.Resources;
using Microsoft.Phone.Controls;
using OmegaCrusher_PhoneComponent;

namespace OmegaCrusher
{
    public partial class MainPage : PhoneApplicationPage
    {
        Direct3DBackground m_d3dBackground = null;

        // Constructor
        public MainPage()
        {
            InitializeComponent();
        }

        private void DrawingSurfaceBackground_Loaded(object sender, RoutedEventArgs e)
        {
            if (m_d3dBackground == null)
            {
                m_d3dBackground = new Direct3DBackground();
            }
        }
    }
}

Congrats. You have the following basic structure displayed in your Solution Explorer. At the moment, you should be able to compile the code and launch the application without any issue (which displays currently an empty content).

3

 

Low-Level Plumbing to set a Content Provider to DrawingSurfaceBackgroundGrid control

At the moment, we have a basic solution structure with two projects. We need now to inform the managed code that Direct3D content displayed in the surface control is provided by our C++ Windows Phone Runtime component. I will provide some low level details for information purpose, but you can go directly to step 8 and come back later if you prefer to hide this plumbing part at the moment :).

Actually, DrawingSurfaceBackgroundGrid is dealing for us Direct3D initialization by creating the Direct3D device and a swap chain for Windows Phone (I will be more focused on this point on a second post). In order to use Direct3D native interfaces  and consume APIs on C++ part, this control should target an object that will perform Direct3D drawing operations by using SetBackgroundContentProvider method.

However, object targeted must first implement the following two interfaces to consume correctly Direct3D APIs.

-
IDrawingSurfaceBackgroundContentProvider

-
IDrawingSurfaceBackgroundContentProviderNative

One solution (used in default Visual Studio templates) is to add an helper class named Direct3DContentProvider which implements these interfaces. This helper class if a Windows Phone Runtime class. If you are familiar with COM and ATL languages in C++, we will use the same kind of model by using Windows Runtime Template Library to implement our Direct3DContentProvider class by deriving from RuntimeClass.

RuntimeClass represents an instantiated class that inherits the specified number of interfaces, and provides the specified Windows Runtime, classic COM, and weak reference support.

IDrawingSurfaceBackgroundContentProviderNative declares four methods that should be implemented by the component as defined below. You can see that Draw method provides pointers on ID3D11Device1, ID3D11DeviceContext1 and ID3D11RenderTargetView to use Direct3D.

 MIDL_INTERFACE("262C1892-975A-45AA-8FE7-F25C2C4088E3")
IDrawingSurfaceBackgroundContentProviderNative : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE Connect( 
        /* [annotation][in] */ 
        _In_  IDrawingSurfaceRuntimeHostNative *host,
        /* [annotation][in] */ 
        _In_  ID3D11Device1 *hostDevice) = 0;
    
    virtual void STDMETHODCALLTYPE Disconnect( void) = 0;
    
    virtual HRESULT STDMETHODCALLTYPE PrepareResources( 
        /* [annotation][in] */ 
        _In_  const LARGE_INTEGER *presentTargetTime,
        /* [annotation][in][out] */ 
        _Inout_  DrawingSurfaceSizeF *desiredRenderTargetSize) = 0;
    
    virtual HRESULT STDMETHODCALLTYPE Draw( 
        /* [annotation][in] */ 
        _In_  ID3D11Device1 *hostDevice,
        /* [annotation][in] */ 
        _In_  ID3D11DeviceContext1 *hostDeviceContext,
        /* [annotation][in] */ 
        _In_  ID3D11RenderTargetView *hostRenderTargetView) = 0;
    
};

 

-
Connect is called after the SetContentProvider(Object) call has completed. In a future post, we will use this method to create some elements of the 3D scene such as vertex and pixel shaders, mesh index and vertex buffers, …

-
Disconnect is called when the associated DrawingSurfaceBackgroundGrid control leaves the XAML visual tree. We can release resources.

-
Draw is called to update the graphics device, context, and render target. We will render the space ship mesh in this method.

-
PrepareResources method is called by the XAML engine for each frame. This is when the Windows Phone Runtime component can set the requested size of the render target.

 

Our helper class will redirect the implementation of these methods to our main component Direct3DBackground (named m_controller).

8. Add a C++ Class named Direct3DContentProvider as displayed below. This is our helper class which implements IDrawingSurfaceBackgroundContentProvider and IDrawingSurfaceBackgroundContentProviderNative interfaces.

4

9. Update the code with the following lines in Direct3DContentProvider header and code files.

Direct3DContentProvider.h

 #pragma once

#include "pch.h"
#include <wrl/module.h>
#include <Windows.Phone.Graphics.Interop.h>
#include <DrawingSurfaceNative.h>

#include "OmegaCrusher.PhoneComponent.h"

class Direct3DContentProvider : public Microsoft::WRL::RuntimeClass <
    Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::WinRtClassicComMix>,
    ABI::Windows::Phone::Graphics::Interop::IDrawingSurfaceBackgroundContentProvider,
    IDrawingSurfaceBackgroundContentProviderNative >
{
public:
    Direct3DContentProvider(OmegaCrusher_PhoneComponent::Direct3DBackground^ controller);

    // IDrawingSurfaceContentProviderNative
    HRESULT STDMETHODCALLTYPE Connect(_In_ IDrawingSurfaceRuntimeHostNative* host, _In_ ID3D11Device1* device);
    void STDMETHODCALLTYPE Disconnect();

    HRESULT STDMETHODCALLTYPE PrepareResources(_In_ const LARGE_INTEGER* presentTargetTime, _Inout_ DrawingSurfaceSizeF* desiredRenderTargetSize);
    HRESULT STDMETHODCALLTYPE Draw(_In_ ID3D11Device1* device, _In_ ID3D11DeviceContext1* context, _In_ ID3D11RenderTargetView* renderTargetView);

private:
    OmegaCrusher_PhoneComponent::Direct3DBackground^ m_controller;
    Microsoft::WRL::ComPtr<IDrawingSurfaceRuntimeHostNative> m_host;
};

Direct3DContentProvider.cpp

 #include "pch.h"
#include "Direct3DContentProvider.h"

using namespace OmegaCrusher_PhoneComponent;

Direct3DContentProvider::Direct3DContentProvider(Direct3DBackground^ controller) :
    m_controller(controller)
{
    m_controller->RequestAdditionalFrame += ref new RequestAdditionalFrameHandler([=]()
    {
        if (m_host)
        {
            m_host->RequestAdditionalFrame();
        }
    });
}

// IDrawingSurfaceContentProviderNative interface
HRESULT Direct3DContentProvider::Connect(_In_ IDrawingSurfaceRuntimeHostNative* host, _In_ ID3D11Device1* device)
{
    m_host = host;
    return m_controller->Connect(host, device);
}

void Direct3DContentProvider::Disconnect()
{
    m_controller->Disconnect();
    m_host = nullptr;
}

HRESULT Direct3DContentProvider::PrepareResources(_In_ const LARGE_INTEGER* presentTargetTime, _Inout_ DrawingSurfaceSizeF* desiredRenderTargetSize)
{
    return m_controller->PrepareResources(presentTargetTime, desiredRenderTargetSize);
}

HRESULT Direct3DContentProvider::Draw(_In_ ID3D11Device1* device, _In_ ID3D11DeviceContext1* context, _In_ ID3D11RenderTargetView* renderTargetView)
{
    return m_controller->Draw(device, context, renderTargetView);
}

>

What is the purpose of RequestAdditionalFrame event in this previous code ?

We need to trigger this event to request the XAML engine to redraw the UI as soon as possible after the current frame is done. The call RequestAdditionalFrame can be made from anywhere in the Windows Phone Runtime component after Connect has been called and before Disconnect is called. 

10. As we redirect Connect, Disconnect, PrepareResources and Draw calls to Direct3DBackground (and also the event RequestAdditionalFrame delegate), we have to update our class with the following code.

OmegaCrusher.PhoneComponent.h

 #pragma once

#include "pch.h"
#include <DrawingSurfaceNative.h>

namespace OmegaCrusher_PhoneComponent
{
    public delegate void RequestAdditionalFrameHandler();

    public ref class Direct3DBackground sealed
    {
    public:
        Direct3DBackground();

        Windows::Phone::Graphics::Interop::IDrawingSurfaceBackgroundContentProvider^ CreateContentProvider();
        event RequestAdditionalFrameHandler^ RequestAdditionalFrame;

    internal:
        HRESULT Connect(_In_ IDrawingSurfaceRuntimeHostNative* host, _In_ ID3D11Device1* device);
        void Disconnect();

        HRESULT PrepareResources(_In_ const LARGE_INTEGER* presentTargetTime, _Inout_ DrawingSurfaceSizeF* desiredRenderTargetSize);
        HRESULT Draw(_In_ ID3D11Device1* device, _In_ ID3D11DeviceContext1* context, _In_ ID3D11RenderTargetView* renderTargetView);
    };
}

OmegaCrusher.PhoneComponent.cpp

 // OmegaCrusher.PhoneComponent.cpp
#include "pch.h"
#include "OmegaCrusher.PhoneComponent.h"
#include "Direct3DContentProvider.h"

using namespace OmegaCrusher_PhoneComponent;
using namespace Platform;
using namespace Microsoft::WRL;
using namespace Windows::Phone::Graphics::Interop;

Direct3DBackground::Direct3DBackground()
{
}

IDrawingSurfaceBackgroundContentProvider^ Direct3DBackground::CreateContentProvider()
{
    ComPtr<Direct3DContentProvider> provider = Make<Direct3DContentProvider>(this);
    return reinterpret_cast<IDrawingSurfaceBackgroundContentProvider^>(provider.Get());
}

// Interface With Direct3DContentProvider
HRESULT Direct3DBackground::Connect(_In_ IDrawingSurfaceRuntimeHostNative* host, _In_ ID3D11Device1* device)
{
    return S_OK;
}

void Direct3DBackground::Disconnect()
{
}

HRESULT Direct3DBackground::PrepareResources(_In_ const LARGE_INTEGER* presentTargetTime, _Inout_ DrawingSurfaceSizeF* desiredRenderTargetSize)
{
    return S_OK;
}

HRESULT Direct3DBackground::Draw(_In_ ID3D11Device1* device, _In_ ID3D11DeviceContext1* context, _In_ ID3D11RenderTargetView* renderTargetView)
{
    return S_OK;
}

Plumbing is almost done !! Just need to update our managed code to call SetBackgroundContentProvider method as below (in MainPage.xaml.cs).

 private void DrawingSurfaceBackground_Loaded(object sender, RoutedEventArgs e)
{
    if (m_d3dBackground == null)
    {
        m_d3dBackground = new Direct3DBackground();

        // Hook-up native component to DrawingSurfaceBackgroundGrid
        DrawingSurfaceBackground.SetBackgroundContentProvider(m_d3dBackground.CreateContentProvider());
    }
}



Our first Direct3D lines of code

Now you have successfully reach this point ;), you can clear the render target view with a beautiful midnight blue. Update the Draw method in OmegaCrusher.PhoneComponent.cpp with the following code.

 HRESULT Direct3DBackground::Draw(_In_ ID3D11Device1* device, _In_ ID3D11DeviceContext1* context, _In_ ID3D11RenderTargetView* renderTargetView)
{
    // Clear the render target and depth stencil to default values.
    const float midnightBlue [] = { 0.098f, 0.098f, 0.439f, 1.000f };
    context->ClearRenderTargetView(
        renderTargetView,
        midnightBlue
        );

    return S_OK;
}

Quite a bit long just for displaying a blue box ;) but we can now play with Direc3D concepts ! 

5

Download Code