Tag Archives: C#

High performance XAML charts… using Win2D

ScreenCap

Hi! 

Over the last couple of years I got quite a few questions about the Chart control that I ported from Silverlight Toolkit and shared in WinRT XAML Toolkit. Mostly about performance and customizing the design. Well, the update performance is something I recently tweaked a little bit by removing some of the animations, but really – the control is fairly complicated and uses lots of XAML to enable customizing all the different parts and so it works best for static charts with just a few data points. I haven’t actually used the control much, so every time I get asked how to change anything in it – I have to investigate it myself and often get lost in all that code.

Continue reading

Tagged , , ,

Cannot deserialize XBF metadata type list as NullableBoolToBoolConverter was not found in namespace

I was trying to get something working last night and kept hitting this exception when my app was trying to InitializeComponent() at one point.

CannotDeserializeXbf

First-chance exception at 0x750D1D4D in App1.exe: Microsoft C++ exception: Platform::COMException ^ at memory location 0x037CDDC0. HRESULT:0x802B000A The text associated with this error code could not be found.

WinRT information: Windows.UI.Xaml.Markup.XamlParseException: The text associated with this error code could not be found.

Cannot deserialize XBF metadata type list as ‘NullableBoolToBoolConverter’ was not found in namespace ‘WinRTXamlToolkit.Converters’. [Line: 0 Position: 0]

at Windows.UI.Xaml.Application.LoadComponent(Object component, Uri resourceLocator, ComponentResourceLocation componentResourceLocation)

All this seemed complete and utter bogus, since NullableBoolToBoolConverter was obviously there and it would work perfectly well in other projects or solution configurations, so let’s look what was happening.

  • NullableBoolToBoolConverter is defined in WinRTXamlToolkit – a .NET Class Library that I’d typically build as Any CPU. It has a ton of controls, converters and other such goodies and of course it typically works just fine.
  • NullableBoolToBoolConverter is used in WinRTXamlToolkit.Debugging – another .NET Class Library that has an in-app XAML visual tree debugger tool – essentially a TreeView control that displays information about the visual tree of your application – hierarchy of UI elements and their properties. The main control in the library uses NullableBoolToBoolConverter and it normally works when used from a .NET Windows Store app.
  • The problem is that WinRTXamlToolkit.Debugging is a .NET Class Library and there is still a small, but important range of WinRT XAML apps written in C++ that can’t use it, so I created a managed WinRT component library – WinRTXamlToolkit.Debugging.WinRTProxy that C++/CX projects can reference. It’s fairly straightforward to do it seems – you just create a proxy class in the WinRT Component library that has methods that invoke methods in the referenced class library (WinRTXamlToolkit.Debugging) and now you can use it from C++/CX. I prefer to keep WinRTXamlToolkit.Debugging a regular .NET class library because WinRT Components have limitations that would limit the APIs I currently have in WinRTXamlToolkit.Debugging. The proxy methods in the WinRTProxy library still have these limitations, but at least I can use it from a native app. The problem is that it wouldn’t work and keep giving me that exception, so what’s up?

It turns out WinRT XAML generates these files – XamlTypeInfo.g.cs that allow the XAML parser to see the types it can use and I noticed that file was missing in the obj folder of WinRTXamlToolkit.Debugging.WinRTProxy. To get it to generate I simply added one empty UserControl to WinRTXamlToolkit.Debugging.WinRTProxy and everything started working fine! Weird but it works. So now I’m left with figuring out how to wrap this all as a NuGet component with native versions of the .NET libraries, since I’ve previously only packaged Any CPU binaries…

Tagged , , , , , ,

How to safely use your time while you build a project?

So your build takes a while… How do you make use of that time (e.g. check Stack Overflow) and not forget to get back to work when the build is done? Well, you can make a sound. I am not sure if there is a simple built-in option to Visual Studio allows to play sounds on events. It is quite possible there is, but I will use what I know. I found you can play a sounds in command line by typing echo ^G, but it didn’t seem to work for me in a VS project post-build step. I blogged about using batch scripts and C# code together in a single file and here’s a way to beep from a batch script:

Continue reading

Tagged , ,

Windows 8 Development with XAML and C# – New and Missing Controls

Although the Windows 8 XAML platform brings back a lot of the controls that exist in WPF and Silverlight, and adds some completely new ones, there are some controls that you might find missing and wonder what to do. In this article we cover a list of some of these controls, along with some suggested ways to cope with their loss.

Read more on Safari Books Online

Tagged , , ,

Windows 8 Development with XAML and C# – Controls

User interfaces are usually composed of reusable controls that encapsulate the logic for rendering the view, taking input and manipulating data. Windows 8 XAML has a wide range of such controls and this is a terse overview of these controls that you can use to check if you know them all.

Read more on Safari Books Online

Tagged , , ,

Windows 8 Development with XAML and C# – Introduction

Why develop for Windows 8?

Windows 8 is a platform with high potential. Based on the trends, Windows 8 is expected to run on half a billion devices within a year or two. Since previous versions of Windows are already running on over a billion machines today, and upgrading from any existing version will cost a mere $15 to $40 – this is just a deal that is hard to miss. Windows 8 is every bit as stable and incrementally improved in its desktop flavor, but it also has a new and exciting part in its touch-centric start screen and app store support.

Read more on Safari Books Online

Tagged , , ,

Creating a Zoomable ScrollViewer with ZoomSnapPoints in WinRT XAML

The Metro/WinRT XAML ScrollViewer by default allows to zoom in on its contents. That is because its ZoomMode property defaults to “Enabled”. I think in most cases it is actually not the desired behavior and you might want to set ZoomMode to ZoomMode.Disabled. I do understand though that this makes the feature more discoverable and does not hurt much while potentially getting users familiar with the new paradigm of quickly scrolling by zooming that is also displayed in the SemanticZoom control.

For my application I needed to enable users to zoom in on a horizontal StackPanel with a list of buttons so that all the buttons fit on screen. At first I thought I would use the SemanticZoom control, but then I realized I need an actual zoom not a semantic one, so the improved (over WPF/Silverlight) ScrollViewer is an obvious choice.

Continue reading

Tagged , , , ,

Debug Layer in Direct3D 11.1 Metro Style Applications

DirectX developers might (should) be familiar with the Debug Layer. This is a piece of code that you can inject into Direct3D for debugging instrumentation. When you create a D3D device, by default – Direct3D is a very thin layer that allows you to achieve maximum performance from your API calls through the drivers to the hardware. In fact it is so thin, that when an error occurs – you only get one of few error codes. This might make you wonder why that API call you are making is failing. Did you forget to pad that constant buffer struct to a multiple of 16 bytes size? Or perhaps you passed an invalid combination of flags to that other method. Well, for years now – you could get all that information by enabling the Debug Layer, but how do you do that in the WinRT world?

Continue reading

Tagged , , , , ,

Creating DirectX Interop Libraries for XAML Metro Style Apps – Part 1: Direct2D

The XAML-based UI stack for Metro Style Apps is quite rich for the youngest member of the XAML UI frameworks family, but sometimes the basic controls even with their rich APIs are not enough. That is where the most powerful point of extensibility of the XAML UI comes in – the DirectX integration.

With DirectX you can create the highest quality real time Direct3D graphics, high performance Direct2D drawings, apply stunning looking pixel shader effects, read and write images of many different formats with WIC, play back video with the Media Framework and high quality audio with XAudio2. You can do all these things in a XAML-based app, so you can use all the rich UI APIs of the XAML framework to quickly create a beautiful looking application and add some special DirectX touch that will make it unique.

In this article I will show you how to create a libraries for drawing Direct2D or Direct3D scenes in the background of an otherwise XAML-rendered UI.

Continue reading on http://labs.vectorform.com

Where to start

The DirectX and XAML interop article on MSDN talks about 3 different ways to mix XAML and DirectX in WinRT:

  1. SurfaceImageSource – which is good for mostly static scenes that get drawn into an Image control
  2. VirtualSurfaceImageSource – which is similar to SurfaceImageSource, but allows to work with surfaces larger than the size of the screen – perhaps for working with high resolution photos or maps
  3. SwapChainBackgroundPanel – which can be used for high performance graphics with almost the same level of control you would otherwise get if you created a solely DirectX-driven app or game

The last option is what we’ll use to create a DirectX-based background for a XAML app. A lot of the things we will use are already available as part of the Metro style app samples available from MSDN. There is no sample however, with the DirectX stuff packaged as a separate library so that you could use it in any C++, C# or VB application and have some reusable code that you would not have to write repeatedly.

Anatomy of a Direct2D Application

To learn how to create a reusable library it helps to know what the basics are of a DirectX application. When you create a basic Direct2D Application from the template that ships with Visual Studio 11 for Windows 8 Consumer Preview you get this:

The pch files are for the precompiled header with references to DirectX headers and WRL (Windows Runtime C++ Template Library), which is an ATL-inspired library that helps to work with COM in a WinRT app – quite useful since DirectX is built on COM.

DirectXHelper.h contains a simple inline function that throws WinRT platform exceptions for failed HRESULTs. This is useful since these exceptions get projected to CLR Exceptions, so if you are planning on consuming the code with .NET you should get regular .NET exceptions on failed HRESULTs.

DirectXBase is an abstract base class that helps to build basic DirectX applications that use D2D, D3D, WIC or DirectWrite by providing basic access to the global class factories and shared resources. Its main entry point is the Initialize() method that takes an instance of the CoreWindow class, which is the core UI piece of every WinRT app and makes that window into a DirectX-managed one. This is also the thing we’ll need to change when we use the SwapChainBackgroundPanel since we want to only associate that panel with DirectX and not the entire UI. Initialization code is divided into 3 methods:

  1. CreateDeviceIndependentResources that in the case of a Direct2D app creates D2D and DWrite factories,
  2. CreateDeviceResources that creates so called D3D 11 device, its context, D2D device and sets the DPI
  3. CreateWindowSizeDependentResources creates
    1. The swap chain, which is a collection of bitmaps or textures that get presented on screen in quick succession to create an illusion of motion,
    2. The render target view, which tells the GPU what to render to (the back buffer of the swap chain),
    3. The depth stencil texture, which is used to control layering/z-ordering of pixels in the scene,
    4. The view port, which controls the view of the swap chain that is shown on screen,
    5. Finally – it associates the D2D device with the back buffer and sets the text anti-aliasing mode

UpdateForWindowSizeChange() is what you need to call whenever the screen resolution changes (e.g. when the app gets snapped, goes into portrait mode or back or when display monitor configuration changes). It basically clears out old window size dependent resources and calls CreateWindowSizeDependentResources to recreate these in different resolution.
The Render() method is where you put all your scene rendering code calls and is what you call when you want to update the view.
Present() should be called after Render() and it basically flips the buffers in the swap chain or reinitializes everything if something failed.
BasicDirect2DApplication.cpp/.h is named after my application name. It is the main entry point of the application and inherits from DirectXBase, overriding its methods to actually render some content on the screen, so you end up with a simple text displayed in the middle of the screen when the app is run. It also has the code that wires up DirectX to the CoreWindow and controls the rendering events. If you are interested in how to do it for a purely DirectX-based application – you can read the How to set up your Metro style DirectX app to display a view on MSDN. For DirectX-XAML interop – we will do things differently.

Anatomy of a Direct3D Application

The Direct3D app is a bit similar, where Direct3DBase is analogue to the DirectXBase class of the Direct2D app, but has a few more files worth noting:

My BasicDirect3DApplication class in this case does not include any DirectX code except for calling out to CubeRenderer which is the subclass of the Direct3DBase class in this case. The Direct3DBase class in the template D3D app is a bit different from its D2D version. Obviously it does not do anything with D2D or DWrite, so it does not deal with these class factories.
CubeRenderer defines the vertices of a cube and allows to render it animated based on system time.
SimplePixel/VertexShader.hlsl are the pixel and vertex shader files that are used to process the cube vertices and turn them into a rasterized output on your screen.

Creating a DirectX WinRT Component Library

I could just share a complete library, which I will at the end, but I think it helps to know how it is done in the first place, so in case you disagree with something I have done or if something changes in the future versions of the tools which currently are in flux you will be able to do it again yourself.

If you want to create the library you start with an empty C++ WinRT Component Library project. You can remove the stub WinRTComponent.cpp/.h files. Next you should to copy the list of referenced project linker input files (static libraries) from the template DX application to the library. The screenshot below shows where to do it. Note – you need to do it once per each solution configuration, so if you switch between Win32, x64 or ARM you will need to reenter these. The right thing to do then would be to switch between all configurations and set these for all of them now to save some trouble later once you start building the solution for other platforms. Here is the list:

d2d1.lib; d3d11.lib; dxgi.lib; ole32.lib; windowscodecs.lib; dwrite.lib; %(AdditionalDependencies)

The second step is to copy the header file includes from the template DX app pch into your project’s pch file.
Finally, copy the Direct3DBase and DirectXHelper files into the library and we are ready to do some coding. Note that copying and pasting between C++ projects in the Solution Explorer works differently than with say C#. If you do it for files in a C# project – they get copied between projects. If you do it for C++ they only get referenced in another project, so make sure you do not do it and delete a temporary project! You can instead just copy the files in Windows Explorer and add them to the project after making the copy outside.

Creating Direct2DBaseForComposition

Add a new class to the project called Direct2DBaseForComposition. You can do it by pressing Alt+Shift+C in the Solution Explorer. The class will derive from DirectXBase and will hook up to the SwapChainBackgroundPanel instead of the CoreWindow in the way described in the MSDN article, so we’ll need to hide the version of the Initialize method that only takes a CoreWindow reference and add one that takes a SwapChainBackgroundPanel, save the native interface to the panel, modify the swap chain initialization to set its dimensions based on panel size and set scaling to DXGI_SCALING_STRETCH, replace the CreateSwapChainForCoreWindow call with one for CreateSwapChainForComposition, hook up the swap chain to the swap chain panel and finally override the Present() method to call the version of the Initialize() method that takes the swap chain panel when the device gets reset.

#pragma once
#include "DirectXBase.h"
#include "windows.ui.xaml.media.dxinterop.h"

ref class Direct2DBaseForComposition abstract
    : public DirectXBase
{
public:
    Direct2DBaseForComposition(void);
    ~Direct2DBaseForComposition(void);

public:
	virtual void CreateWindowSizeDependentResources() override;
	virtual void Initialize(Windows::UI::Core::CoreWindow^ window) override;
	void Initialize(
		_In_ Windows::UI::Core::CoreWindow^ window,
		_In_ Windows::UI::Xaml::Controls::SwapChainBackgroundPanel^ swapChainPanel);
	virtual void Present() override;

private:
	Microsoft::WRL::ComPtr<ISwapChainBackgroundPanelNative>   m_swapChainNative;
	Windows::UI::Xaml::Controls::SwapChainBackgroundPanel^    m_swapChainPanel;
};

Direct2DBaseForComposition.cpp

#include "pch.h"
#include "Direct2DBaseForComposition.h"

using namespace Microsoft::WRL;
using namespace Windows::ApplicationModel;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::UI::Core;
using namespace Windows::System;
using namespace Windows::Foundation;
using namespace Windows::Graphics::Display;
using namespace D2D1;

Direct2DBaseForComposition::Direct2DBaseForComposition(void)
{
}


Direct2DBaseForComposition::~Direct2DBaseForComposition(void)
{
}

void Direct2DBaseForComposition::Initialize(
	_In_ Windows::UI::Core::CoreWindow^ window)
{
    // Can't reduce visibility of a ref class method in a subclass
	throw Platform::Exception::CreateException(
		1 << 31 | FACILITY_ITF << 16 | 1);
		//ref new Platform::String(L"This method is not supported. Use the other overload."));
}

void Direct2DBaseForComposition::Initialize(
	_In_ Windows::UI::Core::CoreWindow^ window,
	_In_ Windows::UI::Xaml::Controls::SwapChainBackgroundPanel^ swapChainPanel)
{
	m_swapChainPanel = swapChainPanel;

	IInspectable* panelInspectable =
		(IInspectable*) reinterpret_cast<IInspectable*>(
			swapChainPanel);
	panelInspectable->QueryInterface(
		__uuidof(ISwapChainBackgroundPanelNative),
		(void **)&m_swapChainNative);

	DirectXBase::Initialize(window);
}

// Allocate all memory resources that change on a window SizeChanged event.
void Direct2DBaseForComposition::CreateWindowSizeDependentResources()
{ 
    // Store the window bounds so the next time we get a SizeChanged event we can
    // avoid rebuilding everything if the size is identical.
    m_windowBounds = m_window->Bounds;

    // If the swap chain already exists, resize it.
    if(m_swapChain != nullptr)
    {
        DX::ThrowIfFailed(
            m_swapChain->ResizeBuffers(2, 0, 0, DXGI_FORMAT_B8G8R8A8_UNORM, 0)
            );
    }
    // Otherwise, create a new one.
    else
    {
        // Create a descriptor for the swap chain.
        DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
		swapChainDesc.Width = (UINT)m_windowBounds.Width;
		swapChainDesc.Height = (UINT)m_windowBounds.Height;
        swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;           // this is the most common swapchain format
        swapChainDesc.Stereo = false; 
        swapChainDesc.SampleDesc.Count = 1;                          // don't use multi-sampling
        swapChainDesc.SampleDesc.Quality = 0;
        swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        swapChainDesc.BufferCount = 2;                               // use double buffering to enable flip
        swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
        swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // we recommend using this swap effect for all applications
        swapChainDesc.Flags = 0;

        // Once the desired swap chain description is configured, it must be created on the same adapter as our D3D Device

        // First, retrieve the underlying DXGI Device from the D3D Device.
        ComPtr<IDXGIDevice1> dxgiDevice;
        DX::ThrowIfFailed(
            m_d3dDevice.As(&dxgiDevice)
            );

        // Identify the physical adapter (GPU or card) this device is running on.
        ComPtr<IDXGIAdapter> dxgiAdapter;
        DX::ThrowIfFailed(
            dxgiDevice->GetAdapter(&dxgiAdapter)
            );

        // And obtain the factory object that created it.
        ComPtr<IDXGIFactory2> dxgiFactory;
        DX::ThrowIfFailed(
            dxgiAdapter->GetParent(
                __uuidof(IDXGIFactory2), 
                &dxgiFactory
                )
            );

		// Create a swap chain for this window from the DXGI factory.
		DX::ThrowIfFailed(
			dxgiFactory->CreateSwapChainForComposition(
				m_d3dDevice.Get(),
				&swapChainDesc,
				nullptr,    // allow on all displays
				&m_swapChain
				)
			);

        m_swapChainNative->SetSwapChain(m_swapChain.Get());

        // Ensure that DXGI does not queue more than one frame at a time. This both reduces 
        // latency and ensures that the application will only render after each VSync, minimizing 
        // power consumption.
        DX::ThrowIfFailed(
            dxgiDevice->SetMaximumFrameLatency(1)
            );

    }

    // Obtain the backbuffer for this window which will be the final 3D rendertarget.
    ComPtr<ID3D11Texture2D> backBuffer;
    DX::ThrowIfFailed(
        m_swapChain->GetBuffer(
            0,
            __uuidof(ID3D11Texture2D),
            &backBuffer
            )
        );

    // Create a view interface on the rendertarget to use on bind.
    DX::ThrowIfFailed(
        m_d3dDevice->CreateRenderTargetView(
            backBuffer.Get(),
            nullptr,
            &m_renderTargetView
            )
        );

    // Cache the rendertarget dimensions in our helper class for convenient use.
    D3D11_TEXTURE2D_DESC backBufferDesc = {0};
    backBuffer->GetDesc(&backBufferDesc);
    m_renderTargetSize.Width  = static_cast<float>(backBufferDesc.Width);
    m_renderTargetSize.Height = static_cast<float>(backBufferDesc.Height);

    // Create a descriptor for the depth/stencil buffer.
    CD3D11_TEXTURE2D_DESC depthStencilDesc(
        DXGI_FORMAT_D24_UNORM_S8_UINT, 
        backBufferDesc.Width,
        backBufferDesc.Height,
        1,
        1,
        D3D11_BIND_DEPTH_STENCIL
        );

    // Allocate a 2-D surface as the depth/stencil buffer.
    ComPtr<ID3D11Texture2D> depthStencil;
    DX::ThrowIfFailed(
        m_d3dDevice->CreateTexture2D(
            &depthStencilDesc,
            nullptr,
            &depthStencil
            )
        );

    // Create a DepthStencil view on this surface to use on bind.
    DX::ThrowIfFailed(
        m_d3dDevice->CreateDepthStencilView(
            depthStencil.Get(),
            &CD3D11_DEPTH_STENCIL_VIEW_DESC(D3D11_DSV_DIMENSION_TEXTURE2D),
            &m_depthStencilView
            )
        );

    // Create a viewport descriptor of the full window size.
    CD3D11_VIEWPORT viewport(
        0.0f,
        0.0f,
        static_cast<float>(backBufferDesc.Width),
        static_cast<float>(backBufferDesc.Height)
        );

    // Set the current viewport using the descriptor.
    m_d3dContext->RSSetViewports(1, &viewport);

    // Now we set up the Direct2D render target bitmap linked to the swapchain. 
    // Whenever we render to this bitmap, it will be directly rendered to the 
    // swapchain associated with the window.
    D2D1_BITMAP_PROPERTIES1 bitmapProperties = 
        BitmapProperties1(
            D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
            PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
            m_dpi,
            m_dpi
            );

    // Direct2D needs the dxgi version of the backbuffer surface pointer.
    ComPtr<IDXGISurface> dxgiBackBuffer;
    DX::ThrowIfFailed(
        m_swapChain->GetBuffer(
            0,
            __uuidof(IDXGISurface),
            &dxgiBackBuffer
            )
        );

    // Get a D2D surface from the DXGI back buffer to use as the D2D render target.
    DX::ThrowIfFailed(
        m_d2dContext->CreateBitmapFromDxgiSurface(
            dxgiBackBuffer.Get(),
            &bitmapProperties,
            &m_d2dTargetBitmap
            )
        );

    // So now we can set the Direct2D render target.
    m_d2dContext->SetTarget(m_d2dTargetBitmap.Get());

    // Set D2D text anti-alias mode to Grayscale to ensure proper rendering of text on intermediate surfaces.
    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
}

// Method to deliver the final image to the display.
void Direct2DBaseForComposition::Present()
{
    // The first argument instructs DXGI to block until VSync, putting the application
    // to sleep until the next VSync. This ensures we don't waste any cycles rendering
    // frames that will never be displayed to the screen.
    HRESULT hr = m_swapChain->Present(1, 0);

    // If the device was removed either by a disconnect or a driver upgrade, we 
    // must completely reinitialize the renderer.
    if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
    {
        Initialize(m_window, m_swapChainPanel);
    }
    else
    {
        DX::ThrowIfFailed(hr);
    }
}

Adding the Rendering Code

The BasicDirect2DApplication I created earlier has four overrides to DirectXBase class that call back to the base class and then create resources for rendering or do the rendering. We will create another class derived from Direct2DBaseForComposition that will do the same, but call back to Direct2DBaseForComposition in the overrides instead of DirectXBase.

SampleD2DRenderer.h

#pragma once
#include "Direct2DBaseForComposition.h"

ref class SampleD2DRenderer
    : public Direct2DBaseForComposition
{
public:
    SampleD2DRenderer(void);
    ~SampleD2DRenderer(void);

    // DirectXBase Methods
    virtual void CreateDeviceIndependentResources() override;
    virtual void CreateDeviceResources() override;
    virtual void CreateWindowSizeDependentResources() override;
    virtual void Render() override;

private:
    Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_solidBrush;
    Microsoft::WRL::ComPtr<IDWriteTextFormat> m_textFormat;
    Microsoft::WRL::ComPtr<IDWriteTextLayout> m_textLayout;
};

SampleD2DRenderer.cpp

#include "pch.h"
#include "SampleD2DRenderer.h"


SampleD2DRenderer::SampleD2DRenderer(void)
{
}


SampleD2DRenderer::~SampleD2DRenderer(void)
{
}

void SampleD2DRenderer::CreateDeviceIndependentResources()
{
    Direct2DBaseForComposition::CreateDeviceIndependentResources();

    // Create a DirectWrite text format object.
    DX::ThrowIfFailed(
        m_dwriteFactory->CreateTextFormat(
            L"Gabriola",
            NULL,
            DWRITE_FONT_WEIGHT_REGULAR,
            DWRITE_FONT_STYLE_NORMAL,
            DWRITE_FONT_STRETCH_NORMAL,
            64.0f,
            L"en-US", // Locale
            &m_textFormat
            )
        );

    // Center the text horizontally and vertically.
    m_textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
    m_textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
}

void SampleD2DRenderer::CreateDeviceResources()
{
    Direct2DBaseForComposition::CreateDeviceResources();

    DX::ThrowIfFailed(
        m_d2dContext->CreateSolidColorBrush(
            D2D1::ColorF(D2D1::ColorF::MidnightBlue),
            &m_solidBrush
            )
        );
}

void SampleD2DRenderer::CreateWindowSizeDependentResources()
{
    Direct2DBaseForComposition::CreateWindowSizeDependentResources();

    const wchar_t* text = L"Hello, Direct2D!";

    // Create a DirectWrite Text Layout object.
    DX::ThrowIfFailed(
        m_dwriteFactory->CreateTextLayout(
            text,                           // Text to be displayed
            wcslen(text),                   // Length of the text
            m_textFormat.Get(),             // DirectWrite Text Format object
            m_renderTargetSize.Width,       // Width of the Text Layout
            m_renderTargetSize.Height,      // Height of the Text Layout
            &m_textLayout
            )
        );

    // Create a text range corresponding to the entire string.
    DWRITE_TEXT_RANGE textRange = {0};
    textRange.length = wcslen(text);
    textRange.startPosition = 0;

    // Set the font size and weight on the text range.
    m_textLayout->SetFontSize(100.f, textRange);
    m_textLayout->SetFontWeight(DWRITE_FONT_WEIGHT_BOLD, textRange);
}

void SampleD2DRenderer::Render()
{
    m_d2dContext->BeginDraw();

    m_d2dContext->Clear(D2D1::ColorF(D2D1::ColorF::CornflowerBlue));
    m_d2dContext->SetTransform(D2D1::Matrix3x2F::Identity());

    m_d2dContext->DrawTextLayout(
        D2D1::Point2F(0.0f, 0.0f),
        m_textLayout.Get(),
        m_solidBrush.Get()
        );

    HRESULT hr = m_d2dContext->EndDraw();

    if (hr == D2DERR_RECREATE_TARGET)
    {
        m_d2dContext->SetTarget(nullptr);
        m_d2dTargetBitmap = nullptr;
        CreateWindowSizeDependentResources();
    }
    else
    {
        DX::ThrowIfFailed(hr);
    }
}

Interface For a Xaml App

I had problems exposing a SwapChainBackgroundPanel class implementation to a C#-based XAML app, so I leave that to be implemented in the app itself. WinRT components cannot expose derived ref classes across ABI boundaries, so a C++ library cannot have public derived classes for use in a C# application directly. To work around that we can create a sort of proxy class that will simply relay the calls to our SampleD2DRenderer. The class needs to be a public ref sealed class defined in a namespace – like this:

SampleD2DRendererProxy.h

#pragma once
#include "SampleD2DRenderer.h"

namespace D2DRenderer
{
    public ref class SampleD2DRendererProxy sealed
    {
    public:
        SampleD2DRendererProxy(void);
        ~SampleD2DRendererProxy(void);
 
		void Initialize(
			_In_ Windows::UI::Core::CoreWindow^ window,
			_In_ Windows::UI::Xaml::Controls::SwapChainBackgroundPanel^ swapChainPanel);
		void Render();
		void Present();
		void UpdateForWindowSizeChange();

	private:
		SampleD2DRenderer^ m_renderer;
    };
}

SampleD2DRendererProxy.cpp

#include "pch.h"
#include "SampleD2DRendererProxy.h"

namespace D2DRenderer
{
    SampleD2DRendererProxy::SampleD2DRendererProxy(void)
    {
    }


    SampleD2DRendererProxy::~SampleD2DRendererProxy(void)
    {
    }

	void SampleD2DRendererProxy::Initialize(
		_In_ Windows::UI::Core::CoreWindow^ window,
		_In_ Windows::UI::Xaml::Controls::SwapChainBackgroundPanel^ swapChainPanel)
	{
        this->m_renderer = ref new SampleD2DRenderer();
		this->m_renderer->Initialize(window, swapChainPanel);
	}

	void SampleD2DRendererProxy::Render()
	{
		this->m_renderer->Render();
	}

	void SampleD2DRendererProxy::Present()
	{
		this->m_renderer->Present();
	}

	void SampleD2DRendererProxy::UpdateForWindowSizeChange()
	{
		this->m_renderer->UpdateForWindowSizeChange();
	}
}

Hooking It Up To The App

The last step is to just use all this in your XAML app. Start by creating any type of C# XAML App. You need to add a class that will be your SwapChainBackgroundPanel implementation like this:

using D2DRenderer;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

namespace SampleD2DXamlAppCSharp
{
    public class MyD2DSwapChainPanel
        : SwapChainBackgroundPanel
    {
        bool _isInitialized;
        SampleD2DRendererProxy _renderer;

        public MyD2DSwapChainPanel()
        {
            CompositionTarget.Rendering += CompositionTarget_Rendering;
            this.SizeChanged += OnSizeChanged;
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (this.ActualWidth == 0 ||
                this.ActualHeight == 0)
            {
                return;
            }

            if (!_isInitialized)
            {
                _renderer = new SampleD2DRendererProxy();
                _renderer.Initialize(
                    Window.Current.CoreWindow,
                    this);

                _isInitialized = true;
            }
            else
            {
                _renderer.UpdateForWindowSizeChange();
            }
        }

        void CompositionTarget_Rendering(object sender, object e)
        {
            if (_isInitialized)
            {
                _renderer.Render();
                _renderer.Present();
            }
        }
    }
}

The panel needs to be at the root of the visual tree of your application, and it is a subclass of the Grid class, so you can modify the OnLaunched() initialization code in your App.xaml.cs to make the navigation Frame class hosted in the panel:

var rootFrame = new Frame();
rootFrame.Navigate(typeof(GroupedItemsPage), sampleData.ItemGroups);

var dxPanel = new MyD2DSwapChainPanel();
dxPanel.Children.Add(rootFrame);
Window.Current.Content = dxPanel;
Window.Current.Activate();

That’s about it, but if I run the app now…

I Can’t See Anything!

Well, by default the layout root grid of each page has a background set to {StaticResource ApplicationPageBackgroundBrush} which is opaque black – you need to change it to Transparent or remove it altogether and that will let you see what Direct2D renders underneath. It also helps to modify opacity of some foreground UI elements to be able to see more of our gorgeous Direct2D background.

Conclusion

What we ended up with is a library that you can use in an WinRT (Metro Style) app to render Direct2D content behind the main UI created in XAML. You can create that UI using C++ as well as C# or VB, while the Direct2D component can render anything you want in the background. It is useful because as powerful as XAML is – it has some limitations. It lacks WriteableBitmap.Render() method that prohibits saving of the rendered results to an image and it does not allow to apply pixel shader effects to the output. You still cannot do it for the XAML elements, but if you really need to achieve some effect in a limited part of your UI – you can do it with Direct2D.

Give Me The Codez!

You can grab the full code from here.

To Be Continued…

The next post will show how to do the same thing for Direct3D.

Tagged , , , , ,

WinRT XAML Toolkit is Growing

Updated WinRT XAML Toolkit on CodePlex – a set of controls, extensions and helper classes for Windows Runtime XAML applications

Features

Continue reading

Tagged , , , , , ,