PETZOLD BOOK BLOG

Charles Petzold on writing books, reading books, and exercising the internal UTM


Recent Entries
< PreviousBrowse the ArchivesNext >
Subscribe to the RSS Feed

Conversion of September 2013 DirectX Factor to Windows 8.1

September 4, 2013
New York, N.Y.

The September installment of my DirectX Factor column for MSDN Magazine has just gone on line. The column focuses on using DirectX geometries in the context of a Windows 8 application, including building them, rendering them, widening them, and outlining them.

I submitted that column in June, right before the Build conference that featured the introduction of Windows 8.1, so the downloadable code associated with that article was written for Windows 8.0 and used the used the "Direct2D App (XAML)" template in Visual Studio 2012. It was fairly easy to convert the GeometryExperimentation project featured in that column to the new "DirectX App (XAML)" template available in Microsoft Visual Studio Express 2013 Preview for Windows 8.1, and here it is. (A previous blog entry discusses the new templates in more detail.)

The only visual difference in the Windows 8.0 and 8.1 version of GeometryExperimentation involves a change to the MainPage.xaml file. In the Windows 8.0 version I used the SwapChainBackgroundPanel, which is required to fill the entire window. SwapChainBackgroundPanel also derives from Grid, so it can have children, and in this program a StackPanel containing RadioButton controls overlays the SwapChainBackgroundPanel. To keep things simple, I didn't attempt to center the DirectX content relative to the non-overlaid part of the SwapChainBackgroundPanel, so the graphics appear centered relative to the window:

Windows 8.1 makes available the SwapChainPanel, which does not have the restriction that it fill the window. In the 8.1 version of the program, I put the SwapChainPanel in a Grid as a sibling of the StackPanel and RadioButton controls. There was no special work to center the DirectX graphics on this surface:

As I've been getting accustomed to the "DirectX App (XAML)" template, I've begun developing several conventions:

Because I've been working mostly with Direct2D and DirectWrite at the present, after creating a new project I've usually renamed and modified the SampleFpsTextRenderer files, and the name I've chosen is the project name followed by Renderer. So, the GeometryExperimentation project has a GeometryExperimentationMain class created from Visual Studio template, and a GeometryExperimentationRenderer class created by me by modifying the SampleFpsTextRenderer class.

When creating a custom Renderer class, I usually add a CreateWindowSizeDependentResources method called from the Main class. This is almost always required.

At this point I've pretty much surrendered to using the supplied DX::ThrowIfFailed inline function. I don't like to enclose a function call in another function call that checks the return value, partially for aesthetic reasons, and partially for reasons related to debugging, and partially because the resultant code highlights the function that checks the return value when what's really important is the DirectX function inside. But DX::ThrowIfFailed is used extensively by the code generated by the template. Using a different approach for the code that I added increasingly seemed deliberately perverse.

The overall architecture of the project created by the DirectX templates is geared for programs that update the display at the screen refresh rate. With many projects, this is unnecessary and wasteful, and the GeometryExperimentation project is a case in point. With the exception of one option, the graphics only need to be re-rendered when a different option is selected from the radio buttons, or when the program's window changes size, or when the output device needs to be recreated. But how to handle these selective screen updates?

In projects created by the "DirectX App (XAML)" template the rendering logic is spread out over in three classes: First, the DirectXPage class uses a CompositionTarget::Rendering event to update the screen. Here's the event handler in DirectXPage.xaml.cs:

void DirectXPage::OnRendering(Object^ sender, Object^ args)
{
    if (m_windowVisible)
    {
        m_main->Update();

        if (m_main->Render())
        {
            m_deviceResources->Present();
        }
    }
}

In the GeometryExperimentation project, the Update and Render methods in GeometryExperimentationMain looked like this after I renamed the Renderer class and eliminated the 3D renderer:

void GeometryExperimentationMain::Update() 
{
    m_timer.Tick([&]()
    {
        m_geometryExperimentationRenderer->Update(m_timer);
    });
}

bool GeometryExperimentationMain::Render() 
{
    // Don't try to render anything before the first Update.
    if (m_timer.GetFrameCount() == 0)
    {
        return false;
    }

    m_geometryExperimentationRenderer->Render();

    return true;
}

As you can see, these Update and Render methods call Update and Render methods in the GeometryExperimentationRenderer class.

If you want to redraw the screen only when necessary, where would you put the logic? One approach is to put it in DirectXPage, perhaps with a Boolean variable named m_needsRedraw. This variable is set to true whenever something happens that might change the graphics output (such as the window changing size or in response to user input). The CompositionTarget::Rendering handler would use that variable like so:

void DirectXPage::OnRendering(Object^ sender, Object^ args)
{
    if (m_windowVisible && m_needsRedraw)
    {
        m_main->Update();

        if (m_main->Render())
        {
            m_deviceResources->Present();
        }

        m_needsRedraw = false;
    }
}

However, this approach has a subtle problem: If the output device ever needs to be recreated, and if nothing else changes, then m_needsRedraw is not set to true because only Main and the Renderer classes know when this happens.

For that reason (and others) I prefer an approach where the Renderer class determines when the screen needs to be redrawn. This is the class with the m_needsRedraw field, which it sets to true whenever something happens that requires a redraw. However, to get this to work right, the Render method in the Renderer class must return a bool indicating that the screen has been re-rendered. The Render method in GeometryExperimentationRenderer hence looks like this:

bool GeometryExperimentationRenderer::Render()
{
    if (!m_needsRedraw)
        return false;

    // ... rendering code

    m_needsRedraw = false;
    return true;
}

The Render method in GeometryExperimentationMain uses that return value to return a value to the CompositionTarget::Rendering handler in DirectXPage:

bool GeometryExperimentationMain::Render() 
{
    // Don't try to render anything before the first Update.
    if (m_timer.GetFrameCount() == 0)
    {
        return false;
    }

    return m_geometryExperimentationRenderer->Render();
}

If there are multiple renderers, then the Render method in the Main class would return true if the Render method in any Renderer class returns true.

Recall that if the Render method in the Main class does not return true, the Present method in DeviceResources isn't called:

void DirectXPage::OnRendering(Object^ sender, Object^ args)
{
    if (m_windowVisible)
    {
        m_main->Update();

        if (m_main->Render())
        {
            m_deviceResources->Present();
        }
    }
}

This seems very simple, but it took a while to get it right, and I think it's the best solution for the general case. (But things get messier if you're also using asynchronous Pointer input.) In the Windows 8.1 version of GeometryExperimentation, only one RadioButton option ("Animated Dot Offset") requires constant screen redrawing, so the Update method is responsible for setting m_needsRedraw to true in that case:

void GeometryExperimentationRenderer::Update(DX::StepTimer const& timer)
{
    if (m_renderingOption == RenderingOption::AnimatedDotOffset)
    {

        // ... update resources based on StepTimer

        m_needsRedraw = true;
    }
}


Recent Entries
< PreviousBrowse the ArchivesNext >
Subscribe to the RSS Feed

(c) Copyright Charles Petzold
www.charlespetzold.com