Namespaces

Variants
Actions

Please note that as of October 24, 2014, the Nokia Developer Wiki will no longer be accepting user contributions, including new entries, edits and comments, as we begin transitioning to our new home, in the Windows Phone Development Wiki. We plan to move over the majority of the existing entries over the next few weeks. Thanks for all your past and future contributions.

(Difference between revisions)

Creating a Lens application that uses HLSL effects for filters

From Wiki
Jump to: navigation, search
r2d2rigo (Talk | contribs)
(R2d2rigo - Added "Application loop and basic drawing" section)
r2d2rigo (Talk | contribs)
(R2d2rigo - Added "Introduction" and "Creating and drawing a DirectX texture" sections.)
Line 4: Line 4:
 
== Introduction ==
 
== Introduction ==
  
 
+
Instagram, one of the most popular photo applications for iPhone (and later Android) gave the tradition of applying post-processing filters to pictures an artistic twist by simulating the effect of snapping the photos with an old Polaroid or Lomographic camera. These effects, although very computationally expensive, can be achieved with relative ease thanks to the new DirectX APIs available in Windows Phone 8 that allow us to execute the image processing in the GPU. In this tutorial, we will create an application that allows us to preview the camera input, snap a photo and apply an HLSL post-processing effect to it.
  
 
== Creating the base project ==
 
== Creating the base project ==
 +
 +
Open a new instance of Visual Studio and create a project based on the '''Windows Phone App''' template. We will be using a standard C#/XAML application and modify it further to accustom our requirements.
  
 
=== Modifying the MainPage ===
 
=== Modifying the MainPage ===
  
Open a new instance of Visual Studio and create a project based on the '''Windows Phone App''' template. We will be using a standard C#/XAML application and modify it further to accustom our requirements. Start by opening the '''MainPage.xaml''' file and deleting the Grid control named '''LayoutRoot''' and all its children elements. Now create a control of type '''DrawingSurfaceBackgroundGrid''' as the page's root and give it a name (we will be calling it '''DrawingSurface'''). This is required when your application is going to use full screen mode for advanced graphics rendering through DirectX, so you don't get any performance penalties derived from the XAML composition system. You can find more info about this control in the MSDN article [http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj714079(v=vs.105).aspx Direct3D with XAML apps for Windows Phone 8].
+
Start by opening the '''MainPage.xaml''' file and deleting the Grid control named '''LayoutRoot''' and all its children elements. Now create a control of type '''DrawingSurfaceBackgroundGrid''' as the page's root and give it a name (we will be calling it '''DrawingSurface'''). This is required when your application is going to use full screen mode for advanced graphics rendering through DirectX, so you don't get any performance penalties derived from the XAML composition system. You can find more info about this control in the MSDN article [http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj714079(v=vs.105).aspx Direct3D with XAML apps for Windows Phone 8].
  
 
=== Adding SharpDX references ===
 
=== Adding SharpDX references ===
Line 65: Line 67:
 
</code>
 
</code>
  
When created, the '''GraphicsDeviceManager''' will fetch the appropriate graphics adapter and initialize a valid graphics device that will allow the application to issue draw calls to the screen through the DirectX runtime. This, in turn, will allow us to tell the '''GraphicsDevice''' in the '''Draw''' function to draw whatever we want; for now, we will clear the entire screen to red. Finally, to make this loop run independelty while the application is open, instantiate it in the '''MainPage''' and call its {{Icode|Run}} method with the '''DrawingSurfaceBackgroundGrid''' you created as its parameter:
+
When created, the '''GraphicsDeviceManager''' will fetch the appropriate graphics adapter and initialize a valid graphics device that will allow the application to issue draw calls to the screen through the DirectX runtime. This, in turn, will allow us to tell the '''GraphicsDevice''' in the '''Draw''' function to draw whatever we want; for now, we will clear the entire screen to red. Finally, to make this loop run independently while the application is open, instantiate it in the '''MainPage''' and call its {{Icode|Run}} method with the '''DrawingSurfaceBackgroundGrid''' you created as its parameter:
  
 
<code csharp>
 
<code csharp>
Line 89: Line 91:
  
 
== Displaying the camera feed onscreen ==
 
== Displaying the camera feed onscreen ==
 +
 +
Now that we have a working DirectX context, we are going to access the camera API to obtain its preview image and drawing it onscreen.
  
 
=== Creating and drawing a DirectX texture ===
 
=== Creating and drawing a DirectX texture ===
 +
 +
Go to '''MainLoop.cs''' and add a public member variable of type '''SharpDX.Toolkit.Graphics.Texture2D''' (be careful not to use the one in the '''SharpDX.Direct3D11''' namespace!), and a private one of type '''SharpDX.Toolkit.Graphics.SpriteBatch'''. The texture will be drawn onscreen every frame via the {{Icode|SpriteBatch}} and will hold the camera preview data in the future. Now, create an override for the function '''Initialize''' of '''MainLoop'''; this function gets called when the DirectX device and adapter have been successfully created, and will initialize a blank version of our texture and the much needed {{Icode|SpriteBatch}}.
 +
 +
<code csharp>
 +
protected override void Initialize()
 +
{
 +
    CreateTexture(640, 480);
 +
    spriteBatch = new SpriteBatch(GraphicsDevice);
 +
 +
    base.Initialize();
 +
}
 +
</code>
 +
 +
The function {{Icode|CreateTexture}} is just a shortcut for the creation and initialization of the texture, to make the code cleaner. Here is the code:
 +
 +
<code csharp>
 +
private void CreateTexture(int textureWidth, int textureHeight)
 +
{
 +
    previewTexture = Texture2D.New(GraphicsDevice, textureWidth, textureHeight, PixelFormat.B8G8R8A8.UNorm);
 +
 +
    Color[] data = new Color[textureWidth * textureHeight];
 +
    for (int i = 0; i < textureWidth * textureHeight; i++)
 +
    {
 +
        data[i] = Color.White;
 +
    }
 +
 +
    previewTexture.SetData<Color>(data);
 +
}
 +
</code>
 +
 +
We just create it by calling '''Texture2D.New''' and passing the appropriate arguments. Be careful that the '''PixelFormat''' must be '''B8G8R8A8.UNorm''' since that's the order the camera will return the colour bytes in, and we will be saving an extra swizzling by declaring it this way. Lastly, the function will initialize an array of {{Icode|Color}} objects to {{Icode|White}} and feed it as the initial data to the texture.
 +
 +
{{Note|As of version 2.4.1, SharpDX doesn't support backbuffer orientations other than portrait. We are going to manually rotate and scale the texture when drawing it so it appears in landscape mode.}}
 +
 +
Now we have to modify the {{Icode|Draw}} function so the {{Icode|SpriteBatch}} previously created draws the texture in fullscreen mode:
 +
 +
<code csharp>
 +
protected override void Draw(GameTime gameTime)
 +
{
 +
    GraphicsDevice.Clear(GraphicsDevice.BackBuffer, Color.Red);
 +
 +
    float backBufferXCenter = GraphicsDevice.BackBuffer.Width / 2;
 +
    float backBufferYCenter = GraphicsDevice.BackBuffer.Height / 2;
 +
 +
    float textureXCenter = previewTexture.Width / 2;
 +
    float textureYCenter = previewTexture.Height / 2;
 +
 +
    float yScale = (float)GraphicsDevice.BackBuffer.Width / (float)previewTexture.Height;
 +
    float xScale = (float)GraphicsDevice.BackBuffer.Height / (float)previewTexture.Width;
 +
 +
    spriteBatch.Begin();
 +
    spriteBatch.Draw(previewTexture, new Vector2(backBufferXCenter, backBufferYCenter), null, Color.White, (float)Math.PI / 2.0f,
 +
        new Vector2(textureXCenter, textureYCenter), new Vector2(xScale, yScale), SpriteEffects.None, 0.0f);
 +
    spriteBatch.End();
 +
 +
    base.Draw(gameTime);
 +
}
 +
</code>
 +
 +
We calculate the center of both the backbuffer and out texture, so we can properly align the drawing origin to the center of the screen. Next, we obtain the scale in both axis in which the texture must be multiplied so it fits in the entire screen without overflowing. And at last, we apply a rotation of Pi/2 radians (90 degrees) to give it the correct landscape orientation. When executed, the white texture should cover all the red background:
 +
 +
<gallery widths="240px" heights="400px">
 +
File:HLSLCamera_SecondScreen.png|DirectX texture covering the entire screen.
 +
File:HLSLCamera_SecondScreen_Scaled.png|Texture scaled to show how it covers the background.
 +
</gallery>
 +
  
 
=== Capturing camera input and updating the texture ===
 
=== Capturing camera input and updating the texture ===
 +
  
 
''Remove Category:Draft when the page is complete or near complete''
 
''Remove Category:Draft when the page is complete or near complete''

Revision as of 01:16, 11 December 2012

This article covers how to create a Lens application that applies different filters to the photos. These filters are programmed in High Level Shading Language and are executed on the GPU to take advantage of the new DirectX functionality introduced in Windows Phone 8.

Contents

Introduction

Instagram, one of the most popular photo applications for iPhone (and later Android) gave the tradition of applying post-processing filters to pictures an artistic twist by simulating the effect of snapping the photos with an old Polaroid or Lomographic camera. These effects, although very computationally expensive, can be achieved with relative ease thanks to the new DirectX APIs available in Windows Phone 8 that allow us to execute the image processing in the GPU. In this tutorial, we will create an application that allows us to preview the camera input, snap a photo and apply an HLSL post-processing effect to it.

Creating the base project

Open a new instance of Visual Studio and create a project based on the Windows Phone App template. We will be using a standard C#/XAML application and modify it further to accustom our requirements.

Modifying the MainPage

Start by opening the MainPage.xaml file and deleting the Grid control named LayoutRoot and all its children elements. Now create a control of type DrawingSurfaceBackgroundGrid as the page's root and give it a name (we will be calling it DrawingSurface). This is required when your application is going to use full screen mode for advanced graphics rendering through DirectX, so you don't get any performance penalties derived from the XAML composition system. You can find more info about this control in the MSDN article Direct3D with XAML apps for Windows Phone 8.

Adding SharpDX references

Instead of using the default DirectX interoperatiblity though a separate C++ DLL, we are going to use the SharpDX library to make drawing calls from C# code. This library is a wrapper of the underlying DirectX functions that allow them to be used in any .NET language and currently supports Windows Desktop, Windows Metro and Windows Phone 8. Start by heading to the downloads section and get the package of your choice: "binary only" includes the libraries and "full package" has some sample code on how to perform common tasks with SharpDX.

Create a new directory inside your project's folder called Lib. Open it and create two child folders called x86 and ARM. Decompress the package you downloaded and from the Bin folder, copy the contents of Standard-wp8-x86 to your x86 folder and Standard-wp8-ARM to ARM. In your project, right click on References and select Add References..., and in the new window click Browse and navigate to the Lib\x86 folder to select the following assemblies:

  • SharpDX.dll
  • SharpDX.DXGI.dll
  • SharpDX.Direct3D11.dll
  • SharpDX.Toolkit.dll
  • SharpDX.Toolkit.Game.dll
  • ShaprDX.Toolkit.Graphics.dll

Since the assemblies aren't AnyCPU, and currently we only have the x86 ones referenced, we must edit our project manually so the compiler references the correct ones. Close Visual Studio and open your CSPROJ file in your favourite text editor and look for the items named Reference, like this one:

<Reference Include="SharpDX">
<HintPath>Lib\x86\SharpDX.dll</HintPath>
</Reference>

You need to change the x86 part of the path to $(Platform), so it ends like this:

<Reference Include="SharpDX">
<HintPath>Lib\$(Platform)\SharpDX.dll</HintPath>
</Reference>

What we have written is a MSBuild property that gets replaced with the current platform name when building the project, so the correct version of the assemblies is used. Repeat this step for all existing references to SharpDX. When finished, save the changes and reopen the solution in Visual Studio.

Note.pngNote: If Visual Studio can't find the SharpDX references, go to the Build > Configuration Manager... menu and change Active solution platform to x86. Remember to change it again to ARM when deploying to a Windows Phone 8 device.

Application loop and basic drawing

Although we are creating a standard XAML navigation-based application, we are going to leverage some of the DirectX functionality to the framework provided by SharpDX.Toolkit. This is a collection of classes and utilities that mimics a subset of the XNA framework, and is provided as an extension to the core SharpDX libraries. If you have previous experience with XNA, you will find some of the code we are going to write very familiar.

Start by creating a new class and naming it MainLoop. Make it inherit from SharpDX.Toolkit.Game' and add a private field of type SharpDX.Toolkit.GraphicsDeviceManager. Now go to the constructor and initialize this field, passing this as the only parameter. At last, override the virtual function Draw and add the line GraphicsDevice.Clear(GraphicsDevice.BackBuffer, Color.Red); to its body. This should be the result:

public class MainLoop : Game
{
GraphicsDeviceManager deviceManager;
 
public MainLoop()
{
deviceManager = new GraphicsDeviceManager(this);
}
 
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(GraphicsDevice.BackBuffer, Color.Red);
 
base.Draw(gameTime);
}
}

When created, the GraphicsDeviceManager will fetch the appropriate graphics adapter and initialize a valid graphics device that will allow the application to issue draw calls to the screen through the DirectX runtime. This, in turn, will allow us to tell the GraphicsDevice in the Draw function to draw whatever we want; for now, we will clear the entire screen to red. Finally, to make this loop run independently while the application is open, instantiate it in the MainPage and call its Run method with the DrawingSurfaceBackgroundGrid you created as its parameter:

public partial class MainPage : PhoneApplicationPage
{
MainLoop loop;
 
public MainPage()
{
InitializeComponent();
 
loop = new MainLoop();
loop.Run(this.DrawingSurface);
}
}

Run your application in the emulator or a device and it should display as follows:

Displaying the camera feed onscreen

Now that we have a working DirectX context, we are going to access the camera API to obtain its preview image and drawing it onscreen.

Creating and drawing a DirectX texture

Go to MainLoop.cs and add a public member variable of type SharpDX.Toolkit.Graphics.Texture2D (be careful not to use the one in the SharpDX.Direct3D11 namespace!), and a private one of type SharpDX.Toolkit.Graphics.SpriteBatch. The texture will be drawn onscreen every frame via the SpriteBatch and will hold the camera preview data in the future. Now, create an override for the function Initialize of MainLoop; this function gets called when the DirectX device and adapter have been successfully created, and will initialize a blank version of our texture and the much needed SpriteBatch.

protected override void Initialize()
{
CreateTexture(640, 480);
spriteBatch = new SpriteBatch(GraphicsDevice);
 
base.Initialize();
}

The function CreateTexture is just a shortcut for the creation and initialization of the texture, to make the code cleaner. Here is the code:

private void CreateTexture(int textureWidth, int textureHeight)
{
previewTexture = Texture2D.New(GraphicsDevice, textureWidth, textureHeight, PixelFormat.B8G8R8A8.UNorm);
 
Color[] data = new Color[textureWidth * textureHeight];
for (int i = 0; i < textureWidth * textureHeight; i++)
{
data[i] = Color.White;
}
 
previewTexture.SetData<Color>(data);
}

We just create it by calling Texture2D.New and passing the appropriate arguments. Be careful that the PixelFormat must be B8G8R8A8.UNorm since that's the order the camera will return the colour bytes in, and we will be saving an extra swizzling by declaring it this way. Lastly, the function will initialize an array of Color objects to White and feed it as the initial data to the texture.

Note.pngNote: As of version 2.4.1, SharpDX doesn't support backbuffer orientations other than portrait. We are going to manually rotate and scale the texture when drawing it so it appears in landscape mode.

Now we have to modify the Draw function so the SpriteBatch previously created draws the texture in fullscreen mode:

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(GraphicsDevice.BackBuffer, Color.Red);
 
float backBufferXCenter = GraphicsDevice.BackBuffer.Width / 2;
float backBufferYCenter = GraphicsDevice.BackBuffer.Height / 2;
 
float textureXCenter = previewTexture.Width / 2;
float textureYCenter = previewTexture.Height / 2;
 
float yScale = (float)GraphicsDevice.BackBuffer.Width / (float)previewTexture.Height;
float xScale = (float)GraphicsDevice.BackBuffer.Height / (float)previewTexture.Width;
 
spriteBatch.Begin();
spriteBatch.Draw(previewTexture, new Vector2(backBufferXCenter, backBufferYCenter), null, Color.White, (float)Math.PI / 2.0f,
new Vector2(textureXCenter, textureYCenter), new Vector2(xScale, yScale), SpriteEffects.None, 0.0f);
spriteBatch.End();
 
base.Draw(gameTime);
}

We calculate the center of both the backbuffer and out texture, so we can properly align the drawing origin to the center of the screen. Next, we obtain the scale in both axis in which the texture must be multiplied so it fits in the entire screen without overflowing. And at last, we apply a rotation of Pi/2 radians (90 degrees) to give it the correct landscape orientation. When executed, the white texture should cover all the red background:


Capturing camera input and updating the texture

Remove Category:Draft when the page is complete or near complete


WP Metro Icon Multimedia.png
WP Metro Icon UI.png
WP Metro Icon DirectX.png
WP Metro Icon WP8.png
Article Metadata
Compatibility
Platform(s):
Windows Phone 8
Article
Created: r2d2rigo (19 Nov 2012)
Last edited: r2d2rigo (11 Dec 2012)
842 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×