![](http://labs.vectorform.com/wp-content/uploads/2012/05/XIsForXAML.png)
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.
The DirectX and XAML interop article on MSDN talks about 3 different ways to mix XAML and DirectX in WinRT:
- SurfaceImageSource – which is good for mostly static scenes that get drawn into an Image control
- 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
- 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.
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 theCoreWindow 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:
- CreateDeviceIndependentResources that in the case of a Direct2D app creates D2D and DWrite factories,
- CreateDeviceResources that creates so called D3D 11 device, its context, D2D device and sets the DPI
- CreateWindowSizeDependentResources creates
- 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,
- The render target view, which tells the GPU what to render to (the back buffer of the swap chain),
- The depth stencil texture, which is used to control layering/z-ordering of pixels in the scene,
- The view port, which controls the view of the swap chain that is shown on screen,
- 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.
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.
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.
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
|
#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);
}
}
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
#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);
}
}
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
#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();
}
}
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
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:
1
2
3
4
5
6
7
|
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…
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.
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.
You can grab the full code from here.
The next post will show how to do the same thing for Direct3D.