Integrating Direct3D with XAML and Windows 8.1

Direct3D Rendering Cookbook

January 2014


For C# .NET developers this is the ultimate cookbook for Direct3D rendering in PC games. Covering all the latest innovations, it teaches everything from debugging to character animation, supported throughout by illustrations and sample code.

(For more resources related to this topic, see here.)

Preparing the swap chain for a Windows Store app

Getting ready

To target Windows 8.1, we need to use Visual Studio 2013. For the remainder of the article, we will assume that these can be located upon navigating to .\External\Bin\DirectX11_2-Signed-winrt under the solution location.

How to do it…

We'll begin by creating a new class library and reusing a majority of the Common project used throughout the book so far, then we will create a new class D3DApplicationWinRT inheriting from D3DApplicationBase to be used as a starting point for our Windows Store app's render targets.

  1. Within Visual Studio, create a new Class Library (Windows Store apps) called Common.WinRT.

    New Project dialog to create a class library project for Windows Store apps

  2. Add references to the following SharpDX assemblies: SharpDX.dll, SharpDX.D3DCompiler.dll, SharpDX.Direct2D1.dll, SharpDX.Direct3D11.dll, and SharpDX.DXGI within .\External\Bin\DirectX11_2-Signed-winrt.
  3. Right-click on the new project; navigate to Add | Existing item... ; and select the following files from the existing Common project: D3DApplicationBase.cs, DeviceManager.cs, Mesh.cs, RendererBase.cs, and HLSLFileIncludeHandlers.hlsl, and optionally, FpsRenderer.cs and TextRenderer.cs.
  4. Instead of duplicating the files, we can choose to Add As Link within the file selection dialog, as shown in the following screenshot:

    Files can be added as a link instead of a copy

  5. Any platform-specific code can be wrapped with a check for the NETFX_CORE definition, as shown in the following snippet:

    #if NETFX_CORE ...Windows Store app code #else ...Windows Desktop code #endif

  6. Add a new C# abstract class called D3DApplicationWinRT.

    // Implements support for swap chain description for // Windows Store apps public abstract class D3DApplicationWinRT : D3DApplicationBase { ... }

  7. In order to reduce the chances of our app being terminated to reclaim system resources, we will use the new SharpDX.DXGI.Device3.Trim function whenever our app is suspended (native equivalent is IDXGIDevice3::Trim). The following code shows how this is done:

    public D3DApplicationWinRT() : base() { // Register application suspending event Windows.ApplicationModel.Core .CoreApplication.Suspending += OnSuspending; } // When suspending hint that resources may be reclaimed private void OnSuspending(Object sender, Windows.ApplicationModel.
    SuspendingEventArgs e) { // Retrieve the DXGI Device3 interface from our // existing Direct3D device. using (SharpDX.DXGI.Device3 dxgiDevice = DeviceManager .Direct3DDevice.QueryInterface<SharpDX.DXGI.Device3>()) { dxgiDevice.Trim(); } }

  8. The existing D3DApplicationBase.CreateSwapChainDescription function is not compatible with Windows Store apps. Therefore, we will override this and create a SwapChainDescription1 instance that is compatible with Windows Store apps. The following code shows the changes necessary:

    protected override SharpDX.DXGI.SwapChainDescription1 CreateSwapChainDescription() { var desc = new SharpDX.DXGI.SwapChainDescription1() { Width = Width, Height = Height, Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm, Stereo = false, SampleDescription.Count = 1, SampleDescription.Quality = 0, Usage = SharpDX.DXGI.Usage.BackBuffer | SharpDX.DXGI.Usage.RenderTargetOutput, Scaling = SharpDX.DXGI.Scaling.Stretch, BufferCount = 2, SwapEffect = SharpDX.DXGI.SwapEffect.FlipSequential, Flags = SharpDX.DXGI.SwapChainFlags.None }; return desc; }

  9. We will not be implementing the Direct3D render loop within a Run method for our Windows Store apps—this is because we will use the existing composition events where appropriate. Therefore, we will create a new abstract method Render and provide a default empty implementation of Run.

    public abstract void Render(); [Obsolete("Use the Render method for WinRT", true)] public override void Run() { }

How it works…

As of Windows 8.1 and DirectX Graphics Infrastructure (DXGI) 1.3, all Direct3D devices created by our Windows Store apps should call SharpDX.DXGI.Device3.Trim when suspending to reduce the memory consumed by the app and graphics driver. This reduces the chance that our app will be terminated to reclaim resources while it is suspended—although our application should consider destroying other resources as well. When resuming, drivers that support trimming will recreate the resources as required.

We have used Windows.ApplicationModel.Core.CoreApplication rather than Windows.UI.Xaml.Application for the Suspending event, so that we can use the class for both an XAML-based Direct3D app as well as one that implements its own Windows.ApplicationModel.Core.IFrameworkView in order to render to CoreWindow directly.

Windows Store apps only support a flip presentation model and therefore require that the swap chain is created using a SharpDX.DXGI.SwapEffect.FlipSequential swap effect; this in turn requires between two and 16 buffers specified in the SwapChainDescription1.BufferCount property. When using a flip model, it is also necessary to specify the SwapChainDescription1.SampleDescription property with Count=1 and Quality=0, as multisample anti-aliasing (MSAA) is not supported on the swap chain buffers themselves. A flip presentation model avoids unnecessarily copying the swap-chain buffer and increases the performance.

By removing Windows 8.1 specific calls (such as the SharpDX.DXGI.Device3.Trim method), it is also possible to implement this recipe using Direct3D 11.1 for Windows Store apps that target Windows 8.

See also

Rendering to a CoreWindow

The XAML view provider found in the Windows Store app graphics framework cannot be modified. Therefore, when we want to implement the application's graphics completely within DirectX/Direct3D without XAML interoperation, it is necessary to create a basic view provider that allows us to connect our DirectX graphics device resources to the windowing infrastructure of our Windows Store app.

In this recipe, we will implement a CoreWindow swap-chain target and look at how to hook Direct3D directly to the windowing infrastructure of a Windows Store app, which is exposed by the CoreApplication, IFrameworkViewSource, IFrameworkView, and CoreWindow .NET types.

This recipe continues from where we left off with the Preparing the swap chain for Windows Store apps recipe.

How to do it…

We will first update the Common.WinRT project to support the creation of a swap chain for a Windows Store app's CoreWindow instance and then implement a simple Hello World application.

  1. Let's begin by creating a new abstract class within the Common.WinRT project, called D3DAppCoreWindowTarget and descending from the D3DApplicationWinRT class from our previous recipe. The default constructor accepts the CoreWindow instance and attaches a handler to its SizeChanged event.

    using Windows.UI.Core; using SharpDX; using SharpDX.DXGI; ... public abstract class D3DAppCoreWindowTarget : D3DApplicationWinRT { // The CoreWindow this instance renders to private CoreWindow _window; public CoreWindow Window { get { return _window; } } public D3DAppCoreWindowTarget(CoreWindow window) { _window = window; Window.SizeChanged += (sender, args) => { SizeChanged(); }; } ... }

  2. Within our new class, we will now override the CurrentBounds property and the CreateSwapChain function in order to return the correct size and create the swap chain for the associated CoreWindow.

    // Retrieve current bounds of CoreWindow public override SharpDX.Rectangle CurrentBounds { get { return new SharpDX.Rectangle( (int)_window.Bounds.X, (int)_window.Bounds.Y,
    (int)_window.Bounds.Width, (int)_window.Bounds.Height); } } // Create the swap chain protected override SharpDX.DXGI.SwapChain1 CreateSwapChain( SharpDX.DXGI.Factory2 factory, SharpDX.Direct3D11.Device1 device, SharpDX.DXGI.SwapChainDescription1 desc) { // Create the swap chain for the CoreWindow using (var coreWindow = new ComObject(_window)) return new SwapChain1(factory, device, coreWindow, ref desc); }

    This completes the changes to our Common.WinRT project. Next, we will create a Hello World Direct3D Windows Store app rendering directly to the application's CoreWindow instance.

  3. Visual Studio 2013 does not provide us with a suitable C# project template to create a non-XAML Windows Store app, so we will begin by creating a new C# Windows Store Blank App (XAML) project.
  4. Add references to the SharpDX assemblies: SharpDX.dll, SharpDX.Direct3D11.dll, SharpDX.D3DCompiler.dll, and SharpDX.DXGI.dll. Also, add a reference to the Common.WinRT project.
  5. Next, we remove the two XAML files from the project: App.xaml and MainPage.xaml.
  6. We will replace the previous application entry point, App.xaml, with a new static class called App. This will house the main entry point for our application where we start our Windows Store app using a custom view provider, as shown in the following snippet:

    using Windows.ApplicationModel.Core; using Windows.Graphics.Display; using Windows.UI.Core; ... internal static class App { [MTAThread] private static void Main() { var viewFactory = new D3DAppViewProviderFactory(); CoreApplication.Run(viewFactory); } // The custom view provider factory class D3DAppViewProviderFactory : IFrameworkViewSource { public IFrameworkView CreateView() { return new D3DAppViewProvider(); } } class D3DAppViewProvider : SharpDX.Component, IFrameworkView { ... } }

  7. The implementation of the IFrameworkView members of D3DAppViewProvider allows us to initialize an instance of a concrete descendent of the D3DAppCoreWindowTarget class within SetWindow and to implement the main application loop in the Run method.

    Windows.UI.Core.CoreWindow window;
    D3DApp d3dApp; // descends from D3DAppCoreWindowTarget
    public void Initialize(CoreApplicationView applicationView) { } public void Load(string entryPoint) { } public void SetWindow(Windows.UI.Core.CoreWindow window) { RemoveAndDispose(ref d3dApp); this.window = window; d3dApp = ToDispose(new D3DApp(window)); d3dApp.Initialize(); } public void Uninitialize() { } public void Run() { // Specify the cursor type as the standard arrow. window.PointerCursor = new CoreCursor( CoreCursorType.Arrow, 0); // Activate the application window, making it visible // and enabling it to receive events. window.Activate(); // Set the DPI and handle changes d3dApp.DeviceManager.Dpi = Windows.Graphics.Display .DisplayInformation.GetForCurrentView().LogicalDpi; Windows.Graphics.Display.DisplayInformation .GetForCurrentView().DpiChanged += (sender, args) => { d3dApp.DeviceManager.Dpi = Windows.Graphics.Display .DisplayInformation.GetForCurrentView().LogicalDpi; }; // Enter the render loop. Note that Windows Store apps // should never exit here. while (true) { // Process events incoming to the window. window.Dispatcher.ProcessEvents( CoreProcessEventsOption.ProcessAllIfPresent); // Render frame d3dApp.Render(); } }

  8. The D3DApp class follows the same structure from our previous recipes throughout the book. There are only a few minor differences as highlighted in the following code snippet:

    class D3DApp: Common.D3DAppCoreWindowTarget { public D3DApp(Windows.UI.Core.CoreWindow window) : base(window) { this.VSync=true; } // Private member fields ... protected override void CreateDeviceDependentResources( Common.DeviceManager deviceManager) { ... create all device resources ... and create renderer instances here } // Render frame public override void Render() { var context = this.DeviceManager.Direct3DContext; // OutputMerger targets must be set every frame context.OutputMerger.SetTargets( this.DepthStencilView, this.RenderTargetView); // Clear depthstencil and render target context.ClearDepthStencilView( this.DepthStencilView, SharpDX.Direct3D11.DepthStencilClearFlags.Depth | SharpDX.Direct3D11.DepthStencilClearFlags.Stencil , 1.0f, 0); context.ClearRenderTargetView( this.RenderTargetView, SharpDX.Color.LightBlue); ... setup context pipeline state ... perform rendering commands // Present the render target Present(); } }

  9. The following screenshot shows an example of the output using CubeRenderer, and overlaying the 2D text with the TextRenderer class:

    Output from the simple Hello World sample using the CoreWindow render target

How it works…

As mentioned, Visual Studio 2013 only provides templates for XAML-based Windows Store apps in C#. Therefore, we started with the C# Blank App (XAML) and then removed the XAML classes so that we could use our own view provider.

To create our own basic view provider, it is necessary for us to define a view provider factory and a view provider class: D3DAppViewProviderFactory and D3DAppViewProvider, respectively, in our sample. Within the static Main entry-point, we then create an instance of our view provider factory and tell the application singleton to run our factory using CoreApplication.Run. Our custom view provider implements the four methods of the IFrameworkView interface: Initialize, SetWindow, Load, and Run. Within SetWindow, we create a swap chain for the provided CoreWindow and initialize our D3DAppCoreWindowTarget concrete descendent class.

The D3DAppCoreWindowTarget class connects to the SizeChanged event of CoreWindow within the constructor, provides the size of CoreWindow through the CurrentBounds property, and finally creates a swap chain using the SharpDX.DXGI.SwapChain1 constructor that accepts a CoreWindow instance. The equivalent in native code would be to use the IDXGIFactory2.CreateSwapChainForCoreWindow method.

// Create the swap chain for the CoreWindow using (var coreWindow = new ComObject(_window)) return new SwapChain1(factory, device, coreWindow, ref desc);

Within the D3DAppViewProvider.Run function, we initialize the dots per inch (dpi) for our device resources and enter our main application message loop, invoking the event dispatcher with a call to CoreDispatcher.ProcessEvents. After processing the events, we call the D3DAppCoreWindowTarget.Render method. The message loop here replaces the use of the D3DApplicationBase.Run method we have used in the rest of this book, necessitating a few structural changes to our D3DAppCoreWindowTarget descendants, such as creating renderer instances within the CreateDeviceDependentResources method. There is one critical difference when rendering a frame with a flip model swapchain—and therefore all Windows Store apps—it is that we must set the Output Merger render targets for every frame.

There's more…

Our C# DirectX Windows Store app implementation is very similar to how you would create a DirectX Windows Store app in C++. Visual Studio provides two C++ DirectX templates, DirectX App and DirectX App (XAML). This recipe is a roughly equivalent C# version of the C++ DirectX App template.

The ability to compile HLSL at runtime was unavailable in Windows Store apps on Windows 8, and manually copying the DLL to the build directory only worked within development environments. However, Windows 8.1 now includes the latest version of D3DCompiler (47) with the OS and is available for compiling HLSL at runtime within our Windows Store apps. As the D3DCompiler binaries are part of the OS, there is no need to include a post-build event to copy the d3dcompiler_*.dll file to the build directory. This should still be done for desktop applications under Windows 8.1.

See also

  • For more information about the setup of a custom view provider, and application and windows events, see How to set up your DirectX Windows Store app to display a view on
  • The Loading and compiling resources asynchronously recipe includes an implementation of compiling HLSL code from files

Rendering to an XAML SwapChainPanel

In this recipe, we will render to an XAML SwapChainPanel. This panel allows us to efficiently render using Direct2D/Direct3D within an XAML Windows Store app. By integrating Direct3D into XAML we are able to use XAML to create flexible and dynamic UIs for our DirectX application. Or we can use the power of DirectX to implement advanced 2D or 3D rendering techniques within a wider XAML application.

A swap chain that participates within an XAML composition, such as the SwapChainPanel swap chain, is also known as a composition swap chain. The SwapChainPanel XAML element is new to Windows 8.1.

This recipe continues from where we left off with Preparing the swap chain for Windows Store apps .

How to do it…

As with the previous recipe, we will first update the Common.WinRT project to support the creation of a swap chain for the Windows Store app XAML SwapChainPanel element. We will then create a simple Hello World sample that demonstrates the integration of Direct3D into XAML.

  1. Let's begin by creating a new abstract class called D3DAppSwapChainPanelTarget within the Common.WinRT project, descending from the D3DApplicationWinRT class.

    public abstract class D3DAppSwapChainPanelTarget : D3DApplicationWinRT { private SwapChainPanel panel; private ISwapChainPanelNative nativePanel; public SwapChainPanel SwapChainPanel { get { return panel; } } public D3DAppSwapChainPanelTarget(SwapChainPanel panel) { this.panel = panel; nativePanel = ToDispose(ComObject.As <SharpDX.DXGI.ISwapChainPanelNative>(panel)); this.panel.CompositionScaleChanged += (s, args) => { ScaleChanged(); }; this.panel.SizeChanged += (sender, args) => { SizeChanged(); }; } ... }

  2. Next, we retrieve the current size of the SwapChainPanel element taking into consideration the scale composition (that is, zoom).

    public override int Width { get { return (int)(panel.RenderSize.Width * panel.CompositionScaleX); } } public override int Height { get { return (int)(panel.RenderSize.Height * panel.CompositionScaleY); } } public override SharpDX.Rectangle CurrentBounds { get { return new SharpDX.Rectangle(0, 0, (int)panel.RenderSize.Width, (int)panel.RenderSize.Height); } }

  3. Within Windows 8.1, a composition swap chain's visuals are exposed to touch scaling and translation scenarios via a touch UI. The following code snippet shows how we can apply this transformation:

    protected void ScaleChanged() { // Resize the SwapChain appropriately base.CreateSizeDependentResources(this); // Retrieve SwapChain2 reference and apply 2D scaling using (var swapChain2 = this.SwapChain.QueryInterface<SwapChain2>()) { // 2D affine transform matrix (inverse of scale) Matrix3x2 inverseScale = new Matrix3x2(); inverseScale.M11 = 1.0f / panel.CompositionScaleX; inverseScale.M22 = 1.0f / panel.CompositionScaleY; swapChain2.MatrixTransform = inverseScale; // Update the DPI (affects Direct2D) DeviceManager.Dpi = 96.0f * panel.CompositionScaleX; } }

  4. Next, we override the CreateSwapChainDescription method to apply settings specific to connecting a swap chain to the SwapChainPanel instance with the IDXGISwapChainPanelNative instance.

    protected override SwapChainDescription1 CreateSwapChainDescription() { // Create description in base D3DApplicationWinRT var desc = base.CreateSwapChainDescription(); // Update as SwapChainPanel requires Stretch scaling desc.Scaling = Scaling.Stretch; return desc; } protected override SharpDX.DXGI.SwapChain1 CreateSwapChain(SharpDX.DXGI.Factory2
    factory, SharpDX.Direct3D11.Device1 device, SharpDX.DXGI.SwapChainDescription1 desc) { // Create the swap chain for XAML composition var swapChain = new SwapChain1(factory, device, ref desc); // Attach swap chain to SwapChainPanel nativePanel.SwapChain = swapChain; return swapChain; }

  5. This completes our changes to the Common.WinRT project. Now we will use the D3DAppSwapChainPanelTarget method within a Windows Store app.
  6. Begin by creating a new C# Blank App (XAML) project and adding the SharpDX and the Common.WinRT references.
  7. Add a new blank page to the project, and name it D3DPanel.xaml.
  8. Open the new XAML file in the designer (Shift + F7 ), and change the Page tag to SwapChainPanel, as shown in the following screenshot:

    Changing the Page element to SwapChainPanel

  9. Now, open the C# code for the XAML file (F7 ), and change the class we inherit to SwapChainPanel instead of Page.

    public sealed partial class D3DPanel : SwapChainPanel { ... }

  10. Within the default constructor, we will initialize a new instance of a D3DApp class (that inherits from the D3DAppSwapChainPanelTarget class), and create the rendering loop using the Windows.UI.Xaml.Media.CompositionTarget.Rendering event.

    D3DApp d3dApp; public D3DPanel() { this.InitializeComponent(); // Only use Direct3D if outside of the designer if (!Windows.ApplicationModel.DesignMode .DesignModeEnabled) { d3dApp = new D3DApp(this); d3dApp.Initialize(); CompositionTarget.Rendering += CompositionTarget_Rendering; } } void CompositionTarget_Rendering(object sender, object e) { if (d3dApp != null) d3dApp.Render(); }

  11. The D3DApp class itself is implemented in exactly the same way as we did for the Rendering to a CoreWindow recipe, with the exception that it descends from D3DAppSwapChainPanelTarget and the constructor looks like the following code snippet:

    class D3DApp: Common.D3DAppSwapChainPanelTarget { public D3DApp(Windows.UI.Xaml.Controls.SwapChainPanel panel) : base(panel) { this.VSync = true; } ... }

How it works…

The default constructor accepts a SwapChainPanel instance, attaches a handler to its SizeChanged and CompositionScaleChanged events, and retrieves a reference to the ISwapChainPanelNative interface. The SwapChainPanel XAML control descends from Windows.UI.Xaml.Controls.Grid and therefore, supports layouts for child controls and can be added as a child to other controls. As can be seen from the following sequence of screenshots, the panel allows our Direct3D output to integrate with the XAML UI composition, including user inputs, transitions, and storyboards:

SwapChainPanel with spinning cube, Hello World text, and an XAML stack panel child control, top: zoomed in and zoomed out, bottom: transparency and XAML transformation applied through a storyboard.

Although the panel allows any number of immediate child controls as would a regular grid, the Microsoft guidelines indicate that no more than eight immediate child controls should be used. Another best practice is to avoid placing a control over the entire swap chain panel in order to prevent overdraws. Each child control of the swap chain panel can then contain any number of subsequent child controls depending on the element type; for example, the UI in the top right of each of the previous screenshots is contained within a StackPanel instance that is an immediate child of our SwapChainPanel implementation.

By retrieving the ISwapChainPanelNative interface from the SwapChainPanel instance, we have connected our new swap chain to the panel through the ISwapChainPanelNative.SwapChain property (natively this is done through the ISwapChainPanelNative.SetSwapChain method). The panel then takes care of associating the swap chain with the appropriate area on the screen.

The Windows.UI.Xaml.Media.CompositionTarget class is a static class that represents the display surface on which our application is being drawn. The Rendering event is fired for each frame, allowing us to integrate our Direct3D rendering with the XAML scene. For applications that require more control over the frame rate, a separate rendering loop thread can be implemented, provided there is appropriate synchronization in place to prevent threading issues when resizing/rescaling during the render loop.

As with the previous recipe, we must set the Output Merger render targets in each frame of our D3DApp class's Render method.

There's more…

When changing the size of a composition swap chain, it is possible to set the SwapChain2.SourceSize property provided, the new size is less than or equal to the original swap chain size. This identifies a portion of the swap chain to be used when presenting its contents to the display and is more efficient than using the SwapChain.ResizeBuffers function which forces the swap chain buffers to be physically resized.

There are a couple of other approaches to rendering Direct3D surfaces to XAML controls using brushes. We can use the Windows.UI.Xaml.Media.Imaging.SurfaceImageSource and Windows.UI.Xaml.Media.Imaging.VirtualSurfaceImageSource classes and their corresponding DXGI interfaces, SharpDX.DXGI.ISurfaceImageSourceNative and SharpDX.DXGI.IVirtualSurfaceImageSourceNative respectively. However, these are not suitable for real-time rendering—instead they can be useful for effects or rendering content that requires less frequent updates.

See also

Loading and compiling resources asynchronously

Within Windows Store apps, it is desirable to keep your application as responsive as possible at all times. Instead of showing a static splash screen for the duration of compiling shaders and loading resources, in this recipe we will initialize our renderers and resources using the async/await keywords.

Getting ready

For this recipe, we will continue from where we left off in the previous recipe Rendering to an XAML SwapChainPanel .

How to do it…

We will first make changes to the SwapChainPanel C# class file and then update the CreateDeviceDependentResources implementation to support asynchronous resource creation. Lastly, we will take a look at some of the additional changes necessary within the code for loading meshes and textures.

  1. Let's begin by opening the D3DPanel.xaml.cs file and registering an event handler for the Loaded event within the constructor.

    public D3DPanel() { this.InitializeComponent(); this.Loaded += swapChainPanel_Loaded; CompositionTarget.Rendering += CompositionTarget_Rendering; } private void swapChainPanel_Loaded(object sender, RoutedEventArgs e) { ... }

  2. We will move the initialization of the D3DApp descendent of D3DAppSwapChainPanelTarget from the default constructor into the Loaded event handler.

    // Only use Direct3D if outside of the designer if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled) { d3dApp = new D3DApp(this); d3dApp.Initialize(); }

  3. Within the D3DApp class, we update the CreateDeviceDependentResources signature to include the async keyword as shown in the following snippet:

    protected async override voidCreateDeviceDependentResources(Common.DeviceManager
    deviceManager) { ... }

  4. Now when we compile our shaders, load our meshes, or initialize our renderer instances, we can do something like the following code snippet:

    // Compile shader, the event caller will continue executing using (var bytecode = await HLSLCompiler.CompileFromFileAsync (@"Shaders\VS.hlsl",
    "VSMain", "vs_5_0")) { ... } // Load mesh var meshes = await Mesh.LoadFromFileAsync ("Character.cmo"); // Other CPU-bound workawait Task.Run(() => { ... (e.g. initialize renderers) });

    Awaiting an asynchronous operation means that although our event handler runs on a background thread, the logic within it still runs in a synchronous manner. Where appropriate, we can also start multiple tasks to take advantage of parallel processing (for example, to initialize multiple renderers upon separate threads).

  5. A full example of the CompileFromFileAsync function from the previous snippet is shown in the following code snippet:

    public static async Task<ShaderBytecode> CompileFromFileAsync(string hlslFile,
    string entryPoint, string profile, ShaderMacro[] defines = null) { if (!Path.IsPathRooted(hlslFile)) hlslFile = Path.Combine(Windows.ApplicationModel .Package.Current.InstalledLocation.Path, hlslFile); CompilationResult result = null; // Compile the HLSL in a separate thread and await it await Task.Run(() => { var shaderSource = SharpDX.IO.NativeFile .ReadAllText(hlslFile); // Compile the shader file ShaderFlags flags = ShaderFlags.None; #if DEBUG flags |= ShaderFlags.Debug | ShaderFlags.SkipOptimization; #endif var includeHandler = new HLSLFileIncludeHandler( Path.GetDirectoryName(hlslFile)); result = ShaderBytecode.Compile(shaderSource, entryPoint, profile, flags, EffectFlags.None, defines, includeHandler, Path.GetFileName(hlslFile)); if (!String.IsNullOrEmpty(result.Message)) throw new CompilationException( result.ResultCode, result.Message); }); return result; }

  6. To check when resources are ready, we use a new ResourcesReady flag as shown in following code snippet:

    bool ResourcesReady = false; protected async override voidCreateDeviceDependentResources(Common.DeviceManager
    deviceManager) { ResourcesReady = false; ... ResourcesReady = true; } public override void Render() { if (!ResourcesReady) return; var context = this.DeviceManager.Direct3DContext; ... }

  7. By incorporating some XAML on the MainPage.xaml to show a loading screen or menu, we can keep our application responsive while the resources continue to load. The following screenshot shows a simple example running in the Windows Store app device simulator:

    Running in simulator—top: showing loading message while compiling shaders and loading meshes.

How it works…

The async and await keywords are a language construct that leverages asynchronous support in the .NET Framework 4.5. By using the async modifier, you specify that a method, lambda expression, or anonymous method is asynchronous. The await operator on the other hand, is applied to a task in an asynchronous method (a method decorated with the async modifier) in order to suspend the execution of the method until the awaited task is completed.

In the previous recipe, where we were initializing the D3DApp instance within the constructor, the CreateDeviceDependentResources event handler ends up running synchronously regardless of the async/await commands used. By moving this initialization out of the constructor and into the Loaded event, we are then able to take advantage of an asynchronous implementation.

The async keyword works for us here because CreateDeviceDependentResources is an overridden event handler attached to the DeviceManager instance within the D3DApplicationBase class constructor as shown here: DeviceManager.OnInitialize += CreateDeviceDependentResources;

As the CreateDeviceDependentResources event handler will now continue running asynchronously while the CompositionTarget.Rendering event fires for each frame, we must set a flag to indicate when the resources are ready to be used and then check this flag within the D3DApp.Render function before attempting to render. With the main Direct3D application class implementing asynchronous resource creation, there is no need to use the async/await keywords within other renderers' CreateDeviceDependentResources functions. If we did this, it would be necessary to also check when those renderers' resources are ready before attempting to render a frame.

Loading a mesh at runtime uses the await keyword as well, because the Common.Mesh.LoadFromFileAsync function utilizes the Windows.Storage.StorageFile class for file I/O in Windows Store app. The CompileFromFileAsync method provides a wrapper around the Direct3D 11 Compile function, implementing the async/await keywords. The ability to compile shaders at runtime within Windows Store apps is new to Windows 8.1. For Windows Store apps under Windows 8.0, it was possible to do so within development environments and only if copying the library manually to the build directory.

There's more…

In the downloadable content for this book, available on the Packt website, the projects Ch11_03CreatingResourcesAsync and Common.WinRT also include changes necessary to load textures from a file. Windows Store apps do not support the ShaderResourceView.FromFile function and instead require you to provide your own DirectDraw Surface (DDS) file and image-loading routines.

The static class Common.LoadTexture includes the following functions:

  • LoadBitmap: This loads a bitmap using Windows Imaging Component (WIC)
  • CreateTexture2DFromBitmap: This creates a Texture2D resource from SharpDX.WIC.BitmapSource
  • SRVFromFile and LoadFromFile: These either load a DDS file using a C# ported version of DDS code from Microsoft or load regular image formats using WIC. They return a ShaderResourceView method and/or a SharpDX.Direct3D11.Resource (Texture1D/Texture2D/Texture3D) function.

The only change to the updated MeshRenderer class in the completed project for this recipe is to call LoadTexture.SRVFromFile instead of ShaderResourceView.FromFile, which is no longer available with Windows Store apps.

See also


In this article we prepared the swap chain for a Window store app, rendered to a CoreWindow, rendered to a XAML SwapChainPanel, loaded and compiled resources asynchronously.

Resources for Article:

Further resources on this subject:

Books to Consider

comments powered by Disqus

An Introduction to 3D Printing

Explore the future of manufacturing and design  - read our guide to 3d printing for free