Trying Out the New Windows 8.1 DirectX Project Templates
July 29, 2013
Roscoe, N.Y.
As you probably know, preview versions of Windows 8.1 and Visual Studio Express 2013 were released about a month ago and are available for downloading here. One good way to learn about the new API features of Windows 8.1 is to watch video sessions from Build 2013.
Since last fall I've been focusing my attention on using DirectX in Windows 8 applications, and writing about my explorations in the DirectX Factor column in MSDN Magazine. The first six installments of this column have been about XAudio2, but beginning in the August issue I'll be delving into Direct2D geometries and DirectWrite and the intersections of graphics and text.
Graphics programming with DirectX involves a lot of code overhead, and this overhead increases when you want to integrate DirectX into a Windows 8 application that responds to changes like snap modes and tablet orientation. One approach is to let Visual Studio generate this overhead for you. All you need do is create a project by selecting a predefined project template.
Let's review the situation with Windows 8: For Windows 8 programming in C++, Visual Studio 2012 has two project templates named "Direct2D App (XAML)" and "Direct3D App." These are not quite accurately labeled because either template can be used for 2D or 3D graphics. The real difference is that the "Direct2D App (XAML)" template uses the SwapChainBackgroundPanel as a rendering surface to allow integrating DirectX with XAML graphics and controls, while "Direct3D App" uses the application's CoreWindow as a rendering surface, and is most appropriate for full-screen graphical applications that don't need any XAML-based controls.
The SwapChainBackgroundPanel derives from Grid and can have child elements arranged in rows and columns. However, SwapChainBackgroundPanel has some severe restrictions: It must be the root element of the application's Page derivative, it must have the lowest Z-order of anything else on the page, and it can't have transforms or projections applied to it.
In Windows 8.1, the SwapChainBackgroundPanel has become pretty much obsolete with the introduction of a new class named simply SwapChainPanel. SwapChainPanel also derives from Grid but without the restrictions of SwapChainBackgroundPanel. The SwapChainPanel can appear anywhere in the XAML visual tree. You can set its RenderTransform and Projection properties, and you can put it in a ScrollViewer. (However, if you find yourself desiring the SwapChainPanel to be significantly larger than the screen, you should probably instead use a VirtualSurfaceImageSource as a rendering surface.)
It is also possible (and desirable) for an application to obtain Pointer (i.e., touch, mouse, and pen) events from the SwapChainPanel in a secondary execution thread. To do this, use a secondary thread to call the CreateCoreIndependentInputSource method on the SwapChainPanel to obtain a CoreIndependentInputSource to which you can attach handlers to the various Pointer events. This means that if Pointer input is used to update DirectX graphics, the entire process of processing these events and updating the screen can occur in a secondary thread without the involvement of the user interface thread. (However, if these Pointer events are used to update Windows Runtime elements, then that code must be dispatched to the UI thread.)
The SwapChainPanel and CoreIndependentInputSource features have been incorporated into the new DirectX project templates in Visual Studio 2013 Preview, and the templates have also been extensively revamped and renamed.
The new project templates are more appropriately named "DirectX App (XAML)" and "DirectX App." Either template can be used for 2D or 3D programming (or both), but "DirectX App (XAML)" uses a SwapChainPanel for integration with XAML controls and graphics, while "DirectX App" renders on the program's CoreWindow.
These two templates share a lot of code. What this means is that if you start off with the "DirectX App" template but decide at a later time that you'd really prefer a little XAML in your app, or if you start off with "DirectX App (XAML)" and decide you can do without the XAML, you can move the rendering code from one project type to another with a minimum of fuss.
Here are all the .cpp, .h, .xaml, and .hlsl files generated by these two templates, with the line count of each file in parentheses. The files that span the two columns are common to the two templates:
DirectX App Template | DirectX App (XAML) Template |
---|---|
App.h (50) | App.h (26) |
App.xaml (15) | |
App.cpp (192) | App.xaml.cpp (76) |
DirectXPage.h (60) | |
DirectXPage.xaml (31) | |
DirectXPage.xaml.cpp (190) | |
pch.h (12) | pch.h (15) |
pch.cpp (1) | |
ProjectNameMain.h (35) | |
ProjectNameMain.cpp (98) | |
Content/SampleFpsTextRenderer.h (31) | |
Content/SampleFpsTextRenderer.cpp (112) | |
Content/Sample3DSceneRenderer.h (40) | |
Content/Sample3DSceneRenderer.cpp (293) | |
Content/ShaderStructures.h (19) | |
Content/SampleVertexShader.hlsl (39) | |
Content/SamplePixelShader.hlsl (12) | |
DeviceResources.h (96) | |
DeviceResources.cpp (725) | |
Common/DirectXHelper.h (63) | |
Common/StepTimer.h (182) |
Files shown in black you'll probably be modifying, in some cases extensively. Files shown in blue are samples that you'll probably replace with your own code. Files shown in red you shouldn't need to alter. Notice that one pair of files that I've indicated as ProjectNameMain.h and ProjectNameMain.cpp have filenames that incorporate the name of the project.
Most of the code overhead required for using DirectX graphics is in the DeviceResources class. This class is responsible for creating and maintaining objects of type ID2D1Factory2, IDWriteFactory2, ID2D1DeviceContext1, ID3D11DeviceContext2 and a bunch of others of that sort. These objects are stored as private members but they are accessible through public Get methods, such as GetD2DFactory, GetDWriteFactory and so on.
The DirectXHelper.h file has small inline functions such as ThrowIfFailed for dealing with HRESULT errors and ReadDataAsync for loading High-Level Shader Language (HLSL) files. The StepTimer class allows your application to obtain elapsed time in very versatile ways generally for purposes of timing animations.
The functions in DirectXHelper.h and the StepTimer class both have a namespace of DX. However, the DeviceResources class has a namespace that is the same as the name of the project. This is a flaw, I think. For reasons I'll discuss shortly, I think DeviceResources should also have a DX namespace.
In the "DirectX App" template, the App.h and App.cpp files define a small ref class named Direct3DApplicationSource that implements the IFrameworkViewSource interface, and a larger ref class named App that implements the IFrameworkView interface. The main entry point instantiates Direct3DApplicationSource, which instantiates App.
This App class instantiates the DeviceResources class and then the ProjectNameMain class, passing the DeviceResources object to the ProjectNameMain constructor. ProjectNameMain then instantiates the SampleFpsTextRenderer and Sample3DSceneRenderer classes (or whatever renderer classes the particular project requires), also passing the DeviceResources object to the classes' constructors. In the discussion below, I'll be referring to the application-specific classes that perform rendering as Renderer classes.
Only Direct3DApplicationSource and App are Windows Runtime ref classes. Everything else is a regular C++ class.
In the "DirectX App (XAML)" template, the App ref class derives from Application (as usual in a Windows Runtime application) and App instantiates the DirectXPage ref class, which derives from Page. The DirectXPage class instantiates the DeviceResources class and then the ProjectNameMain class (passing the DeviceResources object to its constructor), which instantiates any Renderer classes it needs, again passing the DeviceResources object to the constructors of those classes.
In the "DirectX App" template the App class handles user input and implements the rendering loop; in the "DirectX App (XAML)" template, the DirectXPage class handles those chores. Regardless which template you use, you can have one or more rendering classes that display 2D or 3D graphics. (The templates include two sample rendering classes: The 2D renderer displays the current rendering rate in frames per second, and the 3D renderer displays a rotating cube.) The ProjectNameMain serves an an intermediary between the class that handles user input and implements the rendering loop, and the class (or classes) that perform the graphics rendering.
Here is how the various major classes in the "DirectXApp (XAML)" template interact. (Obviously the "DirectX App" template is similar.) Arrows indicate method calls:
App | ||||
↓ | ||||
DirectXPage | → | ProjectNameMain | → | ______Renderer |
↓ | ↕ | ↓ | ||
DeviceResources |
The arrow is two-way between ProjectNameMain and DeviceResources because ProjectNameMain registers itself with DeviceResources to be notified through calls named OnDeviceLost and OnDeviceRecreated when the graphics output device is lost or recreated. Otherwise, the architecture is fairly clean.
In this "DirectX App (XAML)" template, the App class is responsible for managing application lifetime and calls methods in DirectXPage named SaveInternalState and LoadInternalState. (However, the SaveInternalState call does not include the SuspendingOperation object for obtaining a deferral. If the SaveInternalState method in DirectXPage needs to perform asynchronous operations to save state, you'll need to fix this.)
The DirectXPage class instantiates both DeviceResources and ProjectNameMain. DirectXPage also installs a bunch of event handlers and makes calls into DeviceResources for setting the current DPI resolution, when the window size changes, when the orientation changes, when display contents are invalidated, and when the SwapChainPanel composition scale or size changes.
DirectXPage also calls the CreateWindowSizeDependentResources in ProjectNameMain whenever anything happens that results in a change to the size of the program's window. ProjectNameMain can make a similar call to a Renderer class. Because ProjectNameMain is notified when the output device is recreated, it can make CreateDeviceDependentResources and ReleaseDeviceDependentResources calls to the Renderer class.
DirectXPage is also responsible for handling user input, whether it comes from Pointer events on the SwapChainPanel or controls on the application AppBar or other controls on the page. The biggest hassle in the architecture defined by these project templates is that any information that DirectXPage needs to pass to a Renderer class needs to go through ProjectNameMain, which serves as an intermediary much like the View Model in MVVM. If the hassle becomes too great, you can always define a method in ProjectNameMain that returns the Renderer object to DirectXPage so it can make calls into the Renderer directly.
DirectXPage also contains the rendering loop, which is implemented in a handler for the Windows Runtime CompositionTarget::Rendering event, an event that is fired in synchronization with XAML screen refresh:
void DirectXPage::OnRendering(Object^ sender, Object^ args)
{
if (m_windowVisible)
{
m_main->Update();
if (m_main->Render())
{
m_deviceResources->Present();
}
}
}
The rendering loop makes calls into the Update and Render methods of ProjectNameMain, following by a call to the Present method in DeviceResources. That's the method that actually transfers graphics output to the screen. Here's the rendering loop as implemented in the App class of the "DirectX App" template:
void App::Run()
{
while (!m_windowClosed)
{
if (m_windowVisible)
{
CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
m_main->Update();
if (m_main->Render())
{
m_deviceResources->Present();
}
}
else
{
CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
}
}
}
The Update and Render methods in ProjectNameMain probably call Update and Render methods in the Renderer classes. I say "probably" because a Renderer class does not derive from any class or implement any interface, so it need not have an Update method at all. But if it does, the ProjectNameMain can use the StepTimer class to ensure that the Update method in the renderer class is called only once per frame. In addition, the StepTimer instance can be passed to the Update method in the renderer class so it can properly prepare animated graphical objects for the Render method.
Here's how Update is defined in ProjectNameMain created by either template:
// Updates the application state once per frame.
void DxSansXamlMain::Update()
{
// Update scene objects.
m_timer.Tick([&]()
{
// TODO: Replace this with your app content update functions.
m_sceneRenderer->Update(m_timer);
m_fpsTextRenderer->Update(m_timer);
});
}
Because this Update method is explicitly calling the two sample Renderer classes, you'll undoubtedly be changing this code. The Render method in ProjectNameMain is somewhat more compex that Update because it performs some 3D overhead.
As you can see, the templates are set up for Renderer classes that need to perform per-frame updates and rendering. Of course, that's not the case in many applications. In those applications you might want to disable the entire rendering loop in App or DirectXPage, or implement some kind of "needs render" flag either in ProjectNameMain or the renderer classes.
I like the idea that everything from ProjectNameMain on down is independent of the front end of the application. You can thus code application-independent renderers, and swap them in and out of projects.
Well, not exactly. The problem is the namespace. Both ProjectNameMain and the Renderer classes are given namespaces by the template that are the same as the project name. These are not ref classes so they really don't even need namespaces. You can simply remove the namespace names, but these classes make calls into DeviceResources, which also has a namespace the same as the project name, so ProjectNameMain and the Renderer classes need to reference that namespace anyway. This is why I said that DeviceResources should really have a namespace of DX so it is more demonstrably project independent. That way, ProjectNameMain and the Renderer classes can have no namespace or a namespace that is also project independent. (It would also be nice if ProjectNameMain had a filename that was project independent!)
When you create a new project using one of these templates, you'll subsequently create a new Renderer class that will contain the bulk of your custom DirectX code. There is no class or interface from which your Renderer class will derive, but it will certainly have a constructor that accepts an object of type DeviceResources and saves that as a private member for future use.
It is also likely that the Renderer class will have Update and Render methods that are called from ProjectNameMain.
Most Renderer classes will also define methods that involve the creation of resources. Graphics rendering in DirectX generally requires resources that fall into three general groups:
- Device-independent resources such as geometries, text-format, and text-layout objects.
- Device-dependent resources such as brushes, shaders, and vertex buffers.
- Window-size dependent resources.
It is most efficient for a Renderer class to create these resources once and save them as private members. The constructor of the Renderer class can perform this initialization.
However, some resources might later need to be recreated. Most dramatically, the output device might change as the application is running. For example, a graphics board might switch to a lesser hardware-assisted graphics mode when a tablet is running on battery power. For this reason, ProjectNameMain generally calls methods in the Renderer class named ReleaseDeviceDependentResources and CreateDeviceDependentResources so the Renderer class can recreate its device-dependent resources.
The application's window size (or resolution in dots per inch) might change in many ways, and this is signaled by a call from ProjectNameMain to a method named CreateWindowSizeDependentResources in the Renderer.
The way the sample Renderer classes are set up, the constructor creates device-independent resources itself, and makes an initial call to its own CreateDeviceDependentResources method, which thereafter is called from ProjectNameMain. However, the Renderer class does not make a call to its own CreateWindowSizeDependentResources method. Calls to this method occur only from ProjectNameMain.
Let's now test out these new Windows 8.1 DirectX project templates by creating a two applications, one from each template. The proof, as they say, is in the making of the pudding.
I first used the "DirectX App (XAML)" template to create a project named DxWithXaml. In the DirectXPage.xaml file I deleted everything except the root element and the SwapChainPanel. In the DirectXPage.xaml.h file, I deleted the declaration of AppBarButton_Click, and in the DirectXPage.xaml.cpp file, I deleted that method and the code that referenced one of the deleted TextBlock elements in the XAML file.
I also deleted all the files in the Content filter/folder, and added two new ones: RotatingSquaresRenderer.h and RotatingSquaresRenderer.cpp. I patterned these two files after the sample Renderer classes. The header file looks like this:
// DxWithXaml project, RotatingSquaresRenderer.h
#pragma once
#include "..\DeviceResources.h"
#include "..\Common\StepTimer.h"
namespace DxWithXaml
{
class RotatingSquaresRenderer
{
public:
RotatingSquaresRenderer(const std::shared_ptr<DeviceResources>& deviceResources);
void CreateDeviceDependentResources();
void ReleaseDeviceDependentResources();
void CreateWindowSizeDependentResources();
void Update(DX::StepTimer const& timer);
void Render();
private:
// Cached pointer to device resources.
std::shared_ptr<DeviceResources> m_deviceResources;
// Resources for rendering
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_blackBrush;
Microsoft::WRL::ComPtr<ID2D1DrawingStateBlock> m_stateBlock;
Microsoft::WRL::ComPtr<ID2D1RectangleGeometry> m_geometry;
D2D1::Matrix3x2F m_center;
float m_t;
};
}
The constructor and all the public methods are called from DxWithXamlMain class. Notice the private member to store the DeviceResources object, as well as application-specific rendering objects and values. The ID2D1SolidColorBrush and ID2D1DrawingStateBlock are device-dependent resources; the ID2D1RectangleGeometry and Matrix3x2F are window-size dependent, and the m_t is set during Update and used during Render.
These rendering objects and values are created or set in the CreateDeviceDependentResources, CreateWindowSizeDependentResources and Update methods in the code file:
// DxWithXaml project, RotatingSquaresRenderer.cpp
#include "pch.h"
#include "RotatingSquaresRenderer.h"
#include "Common/DirectXHelper.h" // For ThrowIfFailed
using namespace DxWithXaml;
RotatingSquaresRenderer::RotatingSquaresRenderer
(const std::shared_ptr<DeviceResources>& deviceResources) :
m_deviceResources(deviceResources)
{
CreateDeviceDependentResources();
}
void RotatingSquaresRenderer::CreateDeviceDependentResources()
{
DX::ThrowIfFailed(
m_deviceResources->GetD2DDeviceContext()->
CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black),
&m_blackBrush)
);
DX::ThrowIfFailed(
m_deviceResources->GetD2DFactory()->
CreateDrawingStateBlock(&m_stateBlock)
);
}
void RotatingSquaresRenderer::ReleaseDeviceDependentResources()
{
m_blackBrush.Reset();
m_stateBlock.Reset();
}
void RotatingSquaresRenderer::CreateWindowSizeDependentResources()
{
Windows::Foundation::Size outputBounds =
m_deviceResources->GetOutputBounds();
float halfSide = min(outputBounds.Width, outputBounds.Height) / 3;
D2D1_RECT_F rectangle =
D2D1::RectF(-halfSide, -halfSide, halfSide, halfSide);
DX::ThrowIfFailed(
m_deviceResources->GetD2DFactory()->
CreateRectangleGeometry(rectangle, &m_geometry));
m_center = D2D1::Matrix3x2F::Translation(outputBounds.Width / 2,
outputBounds.Height / 2);
}
void RotatingSquaresRenderer::Update(DX::StepTimer const& timer)
{
// Calculate t = 0 to 1 every 3 seconds
m_t = (float) (std::fmod(timer.GetTotalSeconds(), 3.0) / 3.0);
}
void RotatingSquaresRenderer::Render()
{
ID2D1DeviceContext* context = m_deviceResources->GetD2DDeviceContext();
context->SaveDrawingState(m_stateBlock.Get());
context->BeginDraw();
context->Clear(D2D1::ColorF(D2D1::ColorF::AliceBlue));
D2D1::Matrix3x2F orientation =
m_deviceResources->GetOrientationTransform2D();
int num = 48;
for (int n = 0; n < num; n++)
{
float angle = 90 * max(0, min(1, 2 * m_t - (float) n / num));
D2D1::Matrix3x2F rotate = D2D1::Matrix3x2F::Rotation(angle);
context->SetTransform(rotate * m_center * orientation);
context->DrawGeometry(m_geometry.Get(), m_blackBrush.Get());
}
// We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
// is lost. It will be handled during the next call to Present.
HRESULT hr = context->EndDraw();
if (hr != D2DERR_RECREATE_TARGET)
{
DX::ThrowIfFailed(hr);
}
context->RestoreDrawingState(m_stateBlock.Get());
}
During Update, the m_t variable is calculated to go from 0 to 1 every 3 seconds. In the Render method, this is used to calculate a series of rotation angles for 48 squares displayed with a call to DrawGeometry.
In accordance with the sample Renderer classes, everything created in the CreateDeviceDependentResources method is destroyed in the ReleaseDeviceDependentResources method by calling the Reset method of the ComPtr used to store the pointer to the object. This shouldn't be necessary, however, since setting a new object to the smart pointer should cause the previous object to be released.
In a program with a single renderer, the calls in Render to SaveDrawingState and RestoreDrawingState can probably also be eliminated.
Here's the DxWithXamlMain.cpp file, very similar to the one geneated by the template but stripped down quite a bit because the application has only one renderer and it does no 3D rendering:
// DxWithXaml project, DxWithXamlMain.cpp
#include "pch.h"
#include "DxWithXamlMain.h"
#include <DirectXColors.h> // For named colors
#include "Common\DirectXHelper.h" // For ThrowIfFailed
using namespace DxWithXaml;
DxWithXamlMain::DxWithXamlMain
(const std::shared_ptr<DeviceResources>& deviceResources) :
m_deviceResources(deviceResources)
{
// Renderer
m_rotatingSquaresRenderer =
std::unique_ptr<RotatingSquaresRenderer>(
new RotatingSquaresRenderer(m_deviceResources));
m_rotatingSquaresRenderer->CreateWindowSizeDependentResources();
// Register device notification
m_deviceResources->RegisterDeviceNotify(this);
}
DxWithXamlMain::~DxWithXamlMain()
{
// Deregister from device notification
m_deviceResources->RegisterDeviceNotify(nullptr);
}
void DxWithXamlMain::CreateWindowSizeDependentResources()
{
m_rotatingSquaresRenderer->CreateWindowSizeDependentResources();
}
void DxWithXamlMain::Update()
{
m_timer.Tick([&]()
{
m_rotatingSquaresRenderer->Update(m_timer);
});
}
bool DxWithXamlMain::Render()
{
// Don't try to render anything before the first Update.
if (m_timer.GetFrameCount() == 0)
return false;
m_rotatingSquaresRenderer->Render();
return true;
}
// Notifies renderers that device resources need to be released.
void DxWithXamlMain::OnDeviceLost()
{
m_rotatingSquaresRenderer->ReleaseDeviceDependentResources();
}
// Notifies renderers that device resources may now be recreated.
void DxWithXamlMain::OnDeviceRecreated()
{
m_rotatingSquaresRenderer->CreateDeviceDependentResources();
CreateWindowSizeDependentResources();
}
I next used the "DirectX App" template to create a project I named DxSansXaml. I deleted everything in the Content filter/folder and copied in the same two RotatingSquaresRenderer files I created in DxWithXaml, with the only change being the namespace name. I also replaced DxSansXamlMain.h and DxSansXamlMain.cpp with DxWithXamlMain.h and DxWithXamlMain.cpp, only changing the filename, class name, and namespace name.
And it worked, which is very nice to know. You can now write DirectX code for Windows 8.1 that doesn't care whether the front end of the application is a CoreWindow or a XAML-based Page derivative.
Both projects are downloadable here.