Hardware Accelerated Graphics With Win2D

In late September, Microsoft showed us the first version of a new graphic library for WinRT, called Win2D. This library is a wrapper over Direct2D for universal apps. The best thing about Win2D is that it provides us access to the powerful DirectX for 2D graphics, without the need to go unmanaged with C++. We have all this power within our C# and XAML projects.

Win2D is a living project, currently in development. The team work uses sprints and every time they finish a sprint, they launch a new version. Until v0.0.7, the current one, you need to download the source code and compile it, they have a public Github repository here: https://github.com/Microsoft/Win2D. But with the latest 0.0.7 version they finally made binaries you can include in your project directly using NuGet. Simply search for Win2D in the package manager or write this sentence in the package console in Visual Studio:

PM> Install-Package Win2D

Now you have Win2D in your project, what can you do with it? There is many things you can do easily with Win2D. Only as a small example, you can have a look at the image below:



During this article, we will see how to get the preceding result, both in Windows 8.1 and Windows Phone 8.1. And sharing 100% of the code.

Draw surface

In every application using Win2D you will need a draw surface. This will define an area of your screen were Win2D can draw his magic. This area is defined by a XAML control called CanvasControl, not to be confused with the standard Canvas element we can find in XAML.

The CanvasControl element is defined in the Microsoft.Graphics.Canvas namespace. You need to add it to your page to use it as in the following:

  1. <Page  
  2.    x:Class="Win2DSampleUniversal.MainPage"  
  3.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  4.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  5.    xmlns:local="using:Win2DSampleUniversal"  
  6.    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
  7.    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
  8.    xmlns:win2d="using:Microsoft.Graphics.Canvas"  
  9.    mc:Ignorable="d">  
  10.    <Grid>  
  11.       <win2d:CanvasControl x:Name="baseCanvas"/>  
  12.    </Grid>  
  13. </Page>  
Now, to actually draw something in our draw surface, we need what is called a draw session, represented by a CanvasDrawingSession object. The only way to get a valid instance of a CanvasDrawingSession is in the DrawingSession property of the CanvasControl Draw event. So you need to add an event handler to the Draw event and write a code like this in the handler:
  1. private void baseCanvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)  
  2. {  
  3.    using (var drawSession = args.DrawingSession)  
  4.    {  
  5.       drawSession.Clear(Colors.Blue);  
  6.   
  7.       Vector2 center = new Vector2()  
  8.       {  
  9.          X = (float)sender.ActualWidth / 2.0f,  
  10.          Y = (float)sender.ActualHeight / 2.0f  
  11.       };  
  12.       float radius = 150;  
  13.   
  14.       drawSession.FillCircle(center, radius, Colors.LightGreen);  
  15.    }  
  16. }  
The CanvasDrawingSession implements IDisposable and consumes a lot of memory, so it is better to use a “using” block to ensure no wasted resources are left behind. Inside the using block, you can access the methods of CanvasDrawingSession. In the code above you call Clear, passing a Windows.UI.Color as the argument. This paints all the CanvasControl areas with the selected color. Next we draw a simple circle using the FillCircle method in CanvasDrawingSession. There are the following two big groups of methods in CanvasDrawingSession: 
  • The ones starting with Fill, like FillCircle, FillRectangle. These methods draw a solid object in the screen, filled with the color indicated as a parameter.
  • The ones starting with Draw, like DrawCircle and DrawEllipse. These methods draw a solid object in the screen, filled transparent, with a border of the indicated color.

So, in the code above, you draw a solid circle filled with a light green color, over a blue background. If you run now the application, you will get a result like the following:



Now you understand how to draw, but the Draw event is launched only one time, when the CanvasControl is invalidated and included in our Page. If you want to make something more impressive than just a green static circle, you will need a way to animate content in screen.

Launching the Draw event periodically

In fact, in a perfect world you would want to draw your scene between 30 and 60 times per second. This is known as Frames Per Second (FPS), the more you are able to get, the more smooth your scene animation will look like.

You can use a DispatcherTimer, with an elapsed value of 30 or 33 milliseconds, to achieve the 60 frames per second mark. Set up the timer in the OnNavigatedTo method of your page, so it is activated immediately after showing the page the first time:

  1. protected override void OnNavigatedTo(NavigationEventArgs e)  
  2. {  
  3.    base.OnNavigatedTo(e);  
  4.   
  5.    this.random = new Random();  
  6.    this.timer = new DispatcherTimer();  
  7.    this.timer.Interval = new TimeSpan(0, 0, 0, 0, 30);  
  8.    this.timer.Tick += timer_Tick;  
  9.    this.timer.Start();  
  10. }  
In the Tick event handler you can call the Invalidate method of CanvasControl, to force the launch of the Draw event:
  1. private void timer_Tick(object sender, object e)  
  2. {  
  3.    this.timer.Stop();  
  4.    baseCanvas.Invalidate();  
  5. }  
As you can see above, the first thing you need to do is stop the timer. If you don't do this then maybe two calls to timer_Tick can overlap if your code consumes more than 30 miliseconds to execute. To avoid this, first stop the timer, then do whatever you need and call the Invalidate method in CanvasControl. In the Tick event handler you don't start the timer again, it is better to wait until the Draw event finishes and start the timer at the end of the Draw event handler, so you are sure all is done and we can proceed to the next frame.

Stars and planets

Now it is time to start thinking about our Universe. You can start by defining a new class called Planet:
  1. public class Planet  
  2. {  
  3.    public Vector2 Position { getset; }  
  4.    public float BodyRadius { getset; }  
  5.    public float RingsRadius { getset; }  
  6.    public CanvasRadialGradientBrush BodyColor { getset; }  
  7.    public Color RingColor { getset; }  
  8.    public float Acceleration { getset; }  
  9. }  
This “entity” will define a planet or a star, allowing us to create multiple objects to be drawn later. It defines a position, size, color and also an acceleration property.

For the color you can use the CanvasRadialGradientBrush type. This object allows you to create a radial gradient, center in the coordinates you set as center and with different colors:
  1. var gradient = new CanvasRadialGradientBrush(baseCanvas, Colors.Yellow, Colors.Orange);  
  2. gradient.Center = new Vector2() { X = 10, Y = 10 };  
  3. gradient.RadiusX = 40.0f;  
  4. gradient.RadiusY = 40.0f;  
If you want to have your gradient centered in the object you are painting, it is very important to establish the same center coordinates in both objects. You can add a new method like this to the code:
  1. private void CreatePlanet(CanvasRadialGradientBrush bodyColor, float bodyRadius, float acceleration)  
  2. {  
  3.    var planet = new Planet();  
  4.    Vector2 center = new Vector2()  
  5.    {  
  6.       X = (float)this.random.Next(0, (int)baseCanvas.ActualWidth),  
  7.       Y = (float)this.random.Next(0, (int)baseCanvas.ActualHeight)  
  8.    };  
  9.    planet.Position = center;  
  10.    planet.BodyRadius = bodyRadius;  
  11.    planet.BodyColor = bodyColor;  
  12.    planet.BodyColor.Center = planet.Position;  
  13.    planet.BodyColor.RadiusX = planet.BodyRadius;  
  14.    planet.BodyColor.RadiusY = planet.BodyRadius;  
  15.    planet.RingColor = planet.BodyColor.Stops.Last().Color;  
  16.    planet.RingsRadius = this.random.Next((int)planet.BodyRadius, (int)planet.BodyRadius * 2);  
  17.    planet.Acceleration = acceleration;  
  18.    this.planets.Add(planet);  
  19. }  
This way, previously to start our “game timer” and drawing objects, we can create a series of planets and stars, with different colors and sizes and also different accelerations:
  1. private async Task CreateObjects()  
  2. {  
  3.    for (int i = 0; i < 125; i++)  
  4.    {  
  5.       CreatePlanet(new CanvasRadialGradientBrush(baseCanvas, Colors.White, Colors.Transparent),   
  6.       this.random.Next(5, 10), (float)this.random.NextDouble());  
  7.    }  
  8.   
  9.    for (int i = 0; i < 7; i++)  
  10.    {  
  11.       CreatePlanet(new CanvasRadialGradientBrush(baseCanvas, Colors.DarkGoldenrod, Colors.Maroon),   
  12.       this.random.Next(20, 30), (float)this.random.Next(1, 2));  
  13.    }  
  14.   
  15.    for (int i = 0; i < 4; i++)  
  16.    {  
  17.       CreatePlanet(new CanvasRadialGradientBrush(baseCanvas, Colors.Goldenrod, Colors.DarkOrange),   
  18.       this.random.Next(40, 50), (float)this.random.Next(3, 4));  
  19.    }  
  20.   
  21.    for (int i = 0; i < 3; i++)  
  22.    {  
  23.       CreatePlanet(new CanvasRadialGradientBrush(baseCanvas, Colors.Yellow, Colors.Orange),   
  24.       this.random.Next(65, 75), (float)this.random.Next(6, 7));  
  25.    }  
  26. }  
Making the far away objects accelerate more slower and the near ones more faster, you can create a nice visual effect of movement.

Now, on your timer tick event handler you can read the acceleration of every object and recalculate the vertical position of it, based on actual position + acceleration, something like this:
  1. private void timer_Tick(object sender, object e)  
  2. {  
  3.    this.timer.Stop();  
  4.   
  5.    foreach (Planet planet in this.planets)  
  6.    {  
  7.       planet.Position = new Vector2()  
  8.       {  
  9.          X = planet.Position.X,  
  10.          Y = planet.Position.Y < baseCanvas.ActualHeight ? planet.Position.Y + planet.Acceleration : -60  
  11.       };  
  12.       planet.BodyColor.Center = planet.Position;  
  13.    }  
  14.   
  15.    baseCanvas.Invalidate();  
  16. }  
This way, every object will move a different amount of pixels.

Finally, on your Draw event handler, you simply need to draw the objects as in the following:
  1. private void baseCanvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)  
  2. {  
  3.    using (var drawSession = args.DrawingSession)  
  4.    {  
  5.       drawSession.Clear(Colors.Black);  
  6.   
  7.       foreach (Planet item in this.planets)  
  8.       {  
  9.          drawSession.FillCircle(item.Position, item.BodyRadius, item.BodyColor);  
  10.          drawSession.DrawCircle(item.Position, item.RingsRadius, item.RingColor);  
  11.       }  
  12.    }  
  13.   
  14.    if (this.timer != null)  
  15.    this.timer.Start();  
  16. }  
The final result should be something like the picture below:



We only need a ship to travel and we are done!

But drawing a ship with only rectangles, ellipses or circles is a difficult task. Better if you can use a predefined image to do the job.

Working with bitmaps

Working with bitmap images in Win2D is fairly simple, you only need to add the PNG file to your project and use it to create a CanvasBitmap object as in the following:
  1. var img = await CanvasBitmap.LoadAsync(baseCanvas, @"Assets\Spaceship.png");  
The CanvasBitmap's LoadAsync method needs two parameters. The first one is the CanvasControl we use to draw our objects, the second one is the current image path in the project. Now you can create a Spaceship class like this:
  1. public class Spaceship  
  2. {  
  3.    public Vector2 Position { getset; }  
  4.   
  5.    public CanvasBitmap Image { getset; }  
  6. }  
Then, you can create a new instance of this class, containing our spaceship, defining the CanvasBitmap to use and the spaceship position in your CanvasControl as in the following:
  1.    this.spaceship = new Spaceship();  
  2.    this.spaceship.Image = await CanvasBitmap.LoadAsync(baseCanvas, @"Assets\Spaceship.png");  
  3.    this.spaceship.Position = new Vector2()  
  4.    {  
  5.       X = (float)(baseCanvas.ActualWidth / 2) - 60,  
  6.       Y = (float)(baseCanvas.ActualHeight / 1.5) - 55  
  7. };  
Finally you only need to add code to the draw event handler to actually “paint” your ship in the desired position as in the following:
  1. if (this.spaceship != null && this.spaceship.Image != null)  
  2. {  
  3.    drawSession.DrawImage(this.spaceship.Image,  
  4.    new Rect(this.spaceship.Position.X, this.spaceship.Position.Y, 120, 110),  
  5.    new Rect(0, 0, 1952, 1857));  
  6. }  
And you are done! If all went well, you can execute the app both in Windows and Windows Phone and get a result like this:



Now we need some indicators in it.

XAML and Win2D

Win2D uses the new SwapChainPanel released in Windows Phone 8.1 and Windows 8.1 so it can be mixed with other XAML content in the same page.

Is as easy as simply but the XAML over the CanvasControl and all will work in the way it is expected to work. You can use this behavior to use XAML for the parts of the screen that don't need to be hardware accelerated, like indicators, texts and images that create the user interface as in the following:
  1. <Grid>  
  2.    <win2d:CanvasControl x:Name="baseCanvas" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"  
  3.       Draw="baseCanvas_Draw"  
  4.       ManipulationMode="All"   
  5.       ManipulationStarted="baseCanvas_ManipulationStarted"  
  6.       ManipulationCompleted="baseCanvas_ManipulationCompleted"  
  7.       ManipulationDelta="baseCanvas_ManipulationDelta"  
  8.       Margin="-50"/>  
  9.   
  10.    <StackPanel VerticalAlignment="Top" HorizontalAlignment="Stretch" IsHitTestVisible="False">  
  11.       <TextBlock Text="Energy:" FontSize="24" FontWeight="Bold" Foreground="Cyan" Margin="24,24,0,0"/>  
  12.       <ProgressBar x:Name="EnergyBar" Minimum="0" Maximum="100" Value="100" Background="Transparent"   
  13.             Margin="24,12,24,0" Height="12">  
  14.          <ProgressBar.Foreground>  
  15.             <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">  
  16.                <GradientStop Offset="0" Color="Red"/>  
  17.                <GradientStop Offset=".5" Color="Yellow"/>  
  18.                <GradientStop Offset="1" Color="Green"/>  
  19.             </LinearGradientBrush>  
  20.          </ProgressBar.Foreground>  
  21.       </ProgressBar>  
  22.    </StackPanel>  
  23. </Grid>  
And finally, you will have a complete sample of Win2D spaceship game, both in Windows and Windows Phone:



There are many possible improvements you can do and need to do indeed, to this code to turn it on a real game, but I think this little article can help you get a first contact with the awesome technology of Win2D.

 


Recommended Free Ebook
Similar Articles
DevsDNA
Development, training and mentoring company focused on Mobile and Augmented reality