Initializing Direct3D in Windows/Windows Phone 8.1 C#/XAML Universal apps with SharpDX

Since the first SharpDX posts in this blog, the preferred method for creating the tutorial’s Direct3D Windows Store apps has been using the CoreWindow approach. This decision was made to focus on the core functionality of DirectX and because XAML interop performance was less than stellar in the first releases of Windows 8. However, time has passed and the state of this relationship has greatly improved, and with the availability of Windows Runtime in Windows Phone with the 8.1 update through Universal Apps, it’s time to explore this exciting new technology.

First of all, if you haven’t heard about Universal Apps yet, it’s a new project type that allows you to share as much code as you want between Windows Store and Windows Phone apps. This has been achieved by sacrificing the old Silverlight application model and enabling Windows Phone devices to run Windows Runtime apps thanks to an updated kernel. Although with minimal differences, the APIs in both operating systems are almost the same – in the case of DirectX, Windows Phone finally has support for Direct2D and DirectWrite among others, and the API version has been bumped up to 11.2. You can even share XAML pages, although providing a custom UX for each platform is strongly recommended.

Creating the base project

It’s as simple as opening your copy of Visual Studio 2013 (Express for Windows version is supported too) and create a new Visual C# > Store Apps > Blank App (Universal Apps). This will create a new solution with three projects: the base Windows 8.1 project, the base Windows Phone 8.1 project and a shared one that will contain all source code and assets common to both platforms.

The default template creates a MainPage page in each platform, but we are going to share the XAML code for now. So, move one of them to the shared project and delete the other. Now open the NuGet package manager and install the SharpDX.Direct3D11 package in both the Windows and Windows Phone projects; this will automatically add its dependencies too (SharpDX and SharpDX.DXGI).

Although usage of a SwapChainBackgroundPanel is highly recommended for fullscreen usage of Direct3D, we are going to use a SwapChainPanel. This is done because the later can be added to the existing visual tree, while a background panel needs to be specified as the first child of the Page element and doesn’t support the frame-based navigation that the application project provides out of the box. Go ahead and add one to the MainPage, and subscribe to its Loaded event:

  1. <SwapChainPanel x:Name=”SwapChainPanel” Loaded=”SwapChainPanel_Loaded”/>

Initializing Direct3D

As in previous tutorials, we are going to follow the same initialization steps, but in this case we will perform them inside the Loaded event of the SwapChainPanel. This is done this way because we need to know the size of the control to create a proper swap chain that exactly fits inside it. Moreover, we will take into account some minor changes:

  • We must query the DisplayInformation class to obtain the current logical DPI of the screen. Although not common in Windows, in Windows Phone devices XAML layout is specified in virtual pixel values that don’t match the physical screen resolution.
  • The swap chain dimensions must be explicitly specified. Using CoreWindow you could specify automatic sizing.
  • We must cast the SwapChainPanel control to the ISwapChainPanelNative interface to properly set its swap chain.
  • When available, we will be using the DirectX 11.2 version of the data types to take full advantage of the new features.

And here are the steps to follow:

  1. Create a new SharpDX.Direct3D11.Device and cast it to Device2:
  1. using (D3D11.Device defaultDevice = new D3D11.Device(D3D.DriverType.Hardware, D3D11.DeviceCreationFlags.Debug))
  2. {
  3.    this.device = defaultDevice.QueryInterface<D3D11.Device2>();
  4. }

  1. Obtain the handle to the SharpDX.Direct3D11.DeviceContext2:
  1. this.deviceContext = this.device.ImmediateContext2;

  1. Find the amount of physical pixels for each virtual pixel by dividing the logical DPI value between 96 (the default DPI amount):
  1. float pixelScale = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi / 96.0f;

  1. Specify the swap chain creation parameters:
  1. DXGI.SwapChainDescription1 swapChainDescription = new DXGI.SwapChainDescription1()
  2. {
  3.     // No transparency.
  4.     AlphaMode = DXGI.AlphaMode.Ignore,
  5.     // Double buffer.
  6.     BufferCount = 2,
  7.     // BGRA 32bit pixel format.
  8.     Format = DXGI.Format.B8G8R8A8_UNorm,
  9.     // Unlike in CoreWindow swap chains, the dimensions must be set.
  10.     Height = (int)(this.SwapChainPanel.RenderSize.Height * pixelScale),
  11.     Width = (int)(this.SwapChainPanel.RenderSize.Width * pixelScale),
  12.     // Default multisampling.
  13.     SampleDescription = new DXGI.SampleDescription(1, 0),
  14.     // In case the control is resized, stretch the swap chain accordingly.
  15.     Scaling = DXGI.Scaling.Stretch,
  16.     // No support for stereo display.
  17.     Stereo = false,
  18.     // Sequential displaying for double buffering.
  19.     SwapEffect = DXGI.SwapEffect.FlipSequential,
  20.     // This swapchain is going to be used as the back buffer.
  21.     Usage = DXGI.Usage.BackBuffer | DXGI.Usage.RenderTargetOutput,
  22. };

  1. Obtain the automatically created SharpDX.DXGI.Factory3 by queriying the Direct3D device for the SharpDX.DXGI.Device3 interface, and obtaining the reference to its parent. Use it to create the swap chain and then cast it to SwapChain2.
  1. using (DXGI.Device3 dxgiDevice3 = this.device.QueryInterface<DXGI.Device3>())
  2. {
  3.     // Get the DXGI factory automatically created when initializing the Direct3D device.
  4.     using (DXGI.Factory3 dxgiFactory3 = dxgiDevice3.Adapter.GetParent<DXGI.Factory3>())
  5.     {
  6.         // Create the swap chain and get the highest version available.
  7.         DXGI.SwapChain1 swapChain1 = new DXGI.SwapChain1(dxgiFactory3, this.device, ref swapChainDescription);
  8.         this.swapChain = swapChain1.QueryInterface<DXGI.SwapChain2>();
  9.     }
  10. }

  1. Obtain the *SharpDX.DXGI.ISwapChainPanelNative reference and set its swap chain:
  1. using (DXGI.ISwapChainPanelNative nativeObject = ComObject.As<DXGI.ISwapChainPanelNative>(this.SwapChainPanel))
  2. {
  3.     // Set its swap chain.
  4.     nativeObject.SwapChain = this.swapChain;
  5. }

  1. Finally, obtain a SharpDX.Direct3D11.Texture2D from the front buffer of the swap chain and create a SharpDX.Direct3D11.RenderTargetView with it so it can be set as the back buffer when drawing:
  1. this.backBufferTexture = D3D11.Texture2D.FromSwapChain<D3D11.Texture2D>(this.swapChain, 0);
  2. this.backBufferView = new D3D11.RenderTargetView(this.device, this.backBufferTexture);

Rendering

Unlike in other DirectX applications where you usually run a loop and use it to update the state and draw everything, XAML apps use events to notify the Direct3D hosts when they need a new frame. In this case, this is achieved by firing the CompositionTarget.Rendering event. It is advised to subscribe to it when your Direct3D initialization has finished successfully, so you don’t execute any code that could potentially access any uninitialized objects. In our case, we are going to subscribe at the end of the SwapChainPanel_Loaded function:

  1. CompositionTarget.Rendering += CompositionTarget_Rendering;

And, for now, we are only going to clear the backbuffer to a solid color and present it:

  1. // Set the active back buffer and clear it.
  2. this.deviceContext.OutputMerger.SetRenderTargets(this.backBufferView);
  3. this.deviceContext.ClearRenderTargetView(this.backBufferView, Color.CornflowerBlue);
  4. // Tell the swap chain to present the buffer.
  5. this.swapChain.Present(1, DXGI.PresentFlags.None, new DXGI.PresentParameters());

Now we can run the project in both Windows and Windows Phone and check the result:

Application running in Windows and Windows Phone emulator side by side

Source code

As always, you can download the source code for this tutorial in its corresponding GitHub repository. This time you will need Visual Studio 2013 to open it due to the new project types used by Universal Apps.

Applying Direct2D built-in effects to bitmaps with SharpDX

One of the top features that makes Direct2D a prime candidate for developing image editing software is the inclusion of effects. These are much like the image filters that applications like Photoshop or paint.net ship with, and are fully hardware accelerated since their underlying implementation is shader-based so they execute in the GPU. An effect can have multiple input images and multiple input parameters but only one output image, which is the result of all the operations that it performs applied sequentially. Internally, an effect has a graph of atomic operations, which can have multiple steps. For example, a Gaussian blur could be implemented like this:

  • One input image.
  • One input variable (blur radius).
  • Two steps:
    • Apply horizontal blur to input image.
    • Apply vertical blur to output of first step.

However, built-in effects are totally transparent to the final user so we can know what they do but not how they do it. We will explore how custom effects work in future examples. In this tutorial we are going to learn how to use some of the built-in effects that Direct2D already implements. These are some of the most commonly used image filtering techniques (like color adjustment and blurring), and are part of the native library. We are going to modify the previous tutorial to perform a hue rotation operation in the player’s bitmap and a shadow effect in the terrain’s one.

Rendering a brush to an offscreen bitmap

First of all, since effects must be applied to bitmaps and we are drawing our terrain portion with a brush, we need to render it to an off-screen bitmap so we can postprocess it. Start by declaring a new SharpDX.Direct2D1.Bitmap1 variable and proceed to initialize it via its constructor. The size would be the total size of the rectangle we were drawing with the brush (in this case, ten times the bitmap’s width and one time the height). Inside the SharpDX.Direct2D1.BitmapProperties1 make sure you:

  • Declare the same pixel format as the backbuffer is using (Format.B8G8R8A8_UNorm and AlphaMode.Premultiplied),
  • Specify the BitmapOptions.Target options so it can be used as a target for drawing.

Now we can modify the drawing code we were using so we set this bitmap as the Direct2D context target, clear it to a transparent background color, reset the drawing transform to Matrix3x2.Identity and draw the rectangle:

  1. // Set the render target for drawing the brush
  2. d2dContext.Target = brushTarget;
  3. // Clear the rendertarget and draw the brush tiling 10 times
  4. d2dContext.BeginDraw();
  5. d2dContext.Clear(Color.Transparent);
  6. d2dContext.Transform = Matrix3x2.Identity;
  7. d2dContext.FillRectangle(newRectangleF(0, 0, terrainBitmap.Size.Width * 10, terrainBitmap.Size.Height), terrainBrush);
  8. d2dContext.EndDraw();

And here is the bitmap as it is rendered offscreen: Image as it is drawn offscreen

Initializing effect instances

Next, we are going to initialize the effect instances. For the hue rotation we only need a SharpDX.Direct2D1.Effects.HueRotation instance, but for the shadow effect we are going to follow this approach:

  • Create a SharpDX.Direct2D1.Effects.Shadow effect and set the offscreen bitmap as its input.
  • Create a SharpDX.Direct2D1.Effects.AffineTransform2D, set the shadow effect instance as its input and set the TransformMatrix to a translation matrix that will add a small offset to the shadow.
  • Blend the original image and the resulting shadow trough a SharpDX.Direct2D1.Effects.Composite effect. We need to set its InputCount to two, the affine transform effect as the first input and the offscreen bitmap as the second (since blending is done back to front).
  1. // Create hue rotation effect
  2. hueRotationEffect = new SharpDX.Direct2D1.Effects.HueRotation(d2dContext);
  3. // Create image shadow effect
  4. shadowEffect = new SharpDX.Direct2D1.Effects.Shadow(d2dContext);
  5. // Create image transform effect
  6. affineTransformEffect = new SharpDX.Direct2D1.Effects.AffineTransform2D(d2dContext);
  7. affineTransformEffect.SetInputEffect(0, shadowEffect);
  8. affineTransformEffect.TransformMatrix = Matrix3x2.Translation(terrainBitmap.PixelSize.Width * 0.25f, terrainBitmap.PixelSize.Height * 0.25f);
  9. // Create composite effect
  10. compositeEffect = new SharpDX.Direct2D1.Effects.Composite(d2dContext);
  11. compositeEffect.InputCount = 2;
  12. compositeEffect.SetInputEffect(0, affineTransformEffect);

Drawing the effects output

At last, we go back to the render block and modify how the bitmaps are drawn. First we update the effects with their updated inputs/values, and then we use DrawImage instead of DrawBitmap to draw the output of an effect:

  1. // Update input images for shadow and composite effects, and draw the resulting image.
  2. shadowEffect.SetInput(0, brushTarget, true);
  3. compositeEffect.SetInput(1, brushTarget, true);
  4. d2dContext.DrawImage(compositeEffect);
  5. // Translate again for drawing the player bitmap.
  6. d2dContext.Transform = Matrix3x2.Translation(halfWidth, halfHeight – playerBitmap.Size.Height);
  7. // Update input image and value for hue rotation effect and draw it.
  8. hueRotationEffect.SetInput(0, playerBitmap, true);
  9. hueRotationEffect.Angle = System.DateTime.Now.Millisecond % 360;
  10. d2dContext.DrawImage(hueRotationEffect);

Here you can see a screen capture of how the scene would look like: Final drawing result

Source code

As always, you can download the source code (C#/VS 2013, SharpDX 2.6.2) for this sample from its GitHub repository.

Loading and drawing bitmaps with Direct2D using SharpDX

Continuing from the tutorial where we learned how to initialize a Direct2D context and perform basic drawing, in this new sample we are going to learn how to load bitmap images and draw them directly or via brushes. So go ahead and read it if you haven’t and clone the source code as we are going to use it as a starting point. We will be needing some images to load and draw, too, so use the ones you have at hand. We will be using the free Platformer Tiles from Kenney.nl. Select two images you like, add them to the project and set their Build Action to Content. Now, in the MyViewProvider class, define two member variables of type SharpDX.Direct2D1.Bitmap1 and one of type SharpDX.Direct2D1.BitmapBrush1. We will use them to store the image data and the brush object properties respectively.

Image loading with WIC

For loading the images, we will be using the Windows Imaging Component API under the SharpDX.WIC namespace, which is implemented in the SharpDX.Direct2D1.dll assembly. This is a native and extensible Windows API that consists of a set of low-level codecs for encoding and decoding commonly used image file formats such as JPG and PNG. Relying on it we will skip the tedious job of implementing our own file format parsers. To manipulate WIC objects, first we need to instantiate an ImagingFactory object, which doesn’t need any constructor parameters. Then, we need to open the file that holds the bitmap data. For this, we can use SharpDX’s NativeFileStream, a very useful class that is similar to the old .NET’s FileStream and is more straightforward than WinRT’s StorageFile. And don’t forget to prepend Package.Current.InstalledLocation.Path to the file’s path because using a relative path won’t work.

  1. ImagingFactory imagingFactory = newImagingFactory();
  2. NativeFileStream fileStream = newNativeFileStream(Package.Current.InstalledLocation.Path + filePath,
  3.     NativeFileMode.Open, NativeFileAccess.Read);

Now we can create a BitmapDecoder instance, passing both the factory and the file stream. A third parameter is needed, we can set it to DecodeOptions.CacheOnDemand for the time being as we won’t be needing to take advantage of special cache handling. With this, now we can call GetFrame on the newly create instance to retrieve the frame index 0 (since a static image will only have one frame, while other filetypes like GIF can have more than one).

  1. BitmapDecoder bitmapDecoder = newBitmapDecoder(imagingFactory, fileStream, DecodeOptions.CacheOnDemand);
  2. BitmapFrameDecode frame = bitmapDecoder.GetFrame(0);

Having obtained the frame, now we can create a SharpDX.Direct2D1.Bitmap1 with it, can’t we? Not so fast. The pixel data is still encoded in the image’s original colour format, and we should be converting all our bitmaps to the same pixel format for the sake of normalization; for this, we can use SharpDX.WIC.FormatConverter. Start by instantiating a copy, passing the imaging factory as the only constructor parameter, and call its Initialize method specifying the bitmap frame we previously extracted and the desired target pixel format. The catch is that default pixel formats are stored as the unique identifier associated to the format encoder/decoder (a unique System.Guid), and the available ones are declared inside SharpDX.WIC.PixelFormat. We will be using RGBA, 32 bits per pixel with premultiplied alpha (PixelFormat.Format32bppPRGBA), but BGRA can be used too (and must be used if working with a software device since it’s the only one supported).

  1. FormatConverter converter = newFormatConverter(imagingFactory);
  2. converter.Initialize(frame, SharpDX.WIC.PixelFormat.Format32bppPRGBA);

Having the correct pixel format, we can finally create the desired SharpDX.Direct2D1.Bitmap1 through the SharpDX.Direct2D1.Bitmap1.FromWicBitmap function. It receives a Direct2D context and a SharpDX.Direct2D1.BitmapSource. And guess who inherits from that class? That’s it, the format converter we previously created.

  1. newBitmap = SharpDX.Direct2D1.Bitmap1.FromWicBitmap(d2dContext, converter);

Creating a bitmap brush

Now we can proceed to create the SharpDX.Direct2D1.BitmapBrush1 brush used for filling geometric shapes. It’s as simple as instantiating it, passing as parameters the Direct2D context, the desired bitmap and a SharpDX.Direct2D1.BitmapBrushProperties1 where we can define the ExtendModeX and ExtendModeY to specify how the brush behaves when drawing regions bigger than the bitmap’s size. You can choose between Clamp, Wrap and Mirror for repeating the last column/row of pixels, repeating the entire image or mirroring it, respectively. The following image shows the Wrap and Mirror modes applied to both axes: Image wrapping (left) and mirroring (right)

  1. terrainBrush = newBitmapBrush1(d2dContext, terrainBitmap, newBitmapBrushProperties1()
  2. {
  3.     ExtendModeX = ExtendMode.Wrap,
  4.     ExtendModeY = ExtendMode.Wrap,
  5. });

Drawing bitmaps and brushes

Finally we can proceed to draw bitmaps to the screen. As before, this must be done between a BeginDraw/EndDraw block. Let’s start by filling a rectangle with the bitmap brush, centered on the screen, and with a width greater than the bitmap’s size to see the wrapping in action.

  1. int halfWidth = this.swapChain.Description.ModeDescription.Width / 2;
  2. int halfHeight = this.swapChain.Description.ModeDescription.Height / 2;
  3. d2dContext.FillRectangle(newRectangleF(halfWidth – 350, halfHeight, 700, 70), terrainBrush);

But wait, something’s not right, is it? Wrong brush tiling This happens because while we are specifying a region to be filled in coordinates relative to the upper left corner of the screen, the brush uses the origin of coordinates as its point of alignment. We can fix this by specifying a translation SharpDX.Matrix3x2 to offset it and changing the rectangle’s X and Y coordinates to (0, 0).

  1. d2dContext.Transform = Matrix3x2.Translation(halfWidth – 350, halfHeight);
  2. d2dContext.FillRectangle(newRectangleF(0, 0, 700, 70), terrainBrush);

Finally, let’s specify another transform matrix and draw a single bitmap. This is achieved via the DrawBitmap function, which receives the bitmap to be drawn, a float value specifying the opacity (alpha value) to be used and an interpolation mode to apply in case the drawing size is smaller or larger than the bitmap’s size.

  1. d2dContext.Transform = Matrix3x2.Translation(halfWidth, halfHeight – playerBitmap.Size.Height);
  2. d2dContext.DrawBitmap(playerBitmap, 1.0f, SharpDX.Direct2D1.BitmapInterpolationMode.Linear);

Say hello to the little alien astronaut!

Source code

As always, the source code can be downloaded from GitHub. This time the SharpDX libraries are referenced from NuGet and the project is a Windows 8.1 application, making it editable only from Visual Studio 2013.