Frames per second (FPS) is the rate at which DirectX refreshes or renders a scene. In this section, we will examine how to display the FPS in the top left corner of our chart. This will provide an example of calculating the FPS, but also of rendering text. Text is very important in a charting application and can be used to render a title for the cart, node positions, axis labels, and many other things. Text is very slow to render, so aim to minimize the amount of text to less than 200 strings or so. The labels of the axis, chart title, node values, and many other things can all be rendered with text, but the FPS will very quickly drop if you render thousands of strings.
We will build on the chart from the last chapter, which displayed the axis lines. Add two member variable floats to the GraphRenderer class for recording the time in the BasicTimer when the GraphRenderer::Update method is run. Also, add an IDWriteTextFormat object which will hold the format of our FPS output, and a black brush which will be used to draw the text.
Create the Text Format instance in the GraphRenderer::CreateDeviceIndependentResources resources method. The text format is used to specify the font, size, and several other text
formatting options.
private:
// Global pan value for moving the chart with the mouse Windows::Foundation::Point m_pan;
// Member variables for displaying FPS
float m_timeDelta; // Time since last update call float m_timeTotal; // Total time of application
Microsoft::WRL::ComPtr<IDWriteTextFormat> m_textFormat;
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_blackBrush;
void GraphRenderer::CreateDeviceIndependentResources() { DirectXBase::CreateDeviceIndependentResources();
DX::ThrowIfFailed(
m_dwriteFactory->CreateTextFormat(
L"Segoe UI", nullptr,
DWRITE_FONT_WEIGHT_NORMAL,
Create the brush for drawing the text in the CreateDeviceDependentResources method.
Add an #include <string> to the GraphRenderer's code file. This gives us functions to append the floats to strings for displaying the frames per second.
Record the times (m_timeTotal and m_timeDelta) passed as parameters to the Update method in the GraphRenderer's code file. m_timeTotal is the total number of milliseconds that have elapsed since the program started, and timeDelta is the amount of time that has elapsed since the last call to Update.
DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 42.0f,
L"en-US",
&m_textFormat )
);
}
void GraphRenderer::CreateDeviceResources() { DirectXBase::CreateDeviceResources();
// Call the create device resources for our graph variable m_graphVariable->CreateDeviceDependentResources(m_d2dContext);
// Create the brush for the origin
m_axes->CreateDeviceDependentResources(m_d2dContext);
// Create the solid brush for the text DX::ThrowIfFailed(
m_d2dContext-
>CreateSolidColorBrush(ColorF(ColorF::Black),&m_blackBrush));
}
// GraphRenderer.cpp
#include "pch.h"
#include <string>
#include "GraphRenderer.h"
In the GraphRenderer::Render method, create the string by appending the times to labels (Total Time and FPS). The inverse of m_timeDelta (1.0f/m_timeDelta) is the speed of the last update. I have rounded this value to an integer. It is a good idea to render our new FPS string after all of the graph objects are drawn so it is not obscured.
Upon running the application, you will note that the timers are only updated when the graph is panned. This is a very good thing for saving power on WinRT devices, but for testing the
performance of our chart rendering, we want to switch the application to real time. To switch to real time (updating repeatedly), open the DirectXPage.cpp file and comment out the “if” condition that causes the program to update based on the m_renderNeeded member variable. The OnRendering method of the DirectXPage is called when the CompositionTarget::Rendering event is fired.
The CompositionTarget is the surface to which the XAML controls are rendered.
void GraphRenderer::Update(float timeTotal, float timeDelta) { // Record the times for displaying:
m_timeDelta = timeDelta;
m_timeTotal = timeTotal;
}
//
// Draw objects here //
// Render the graph variable
m_graphVariable->Render(m_d2dContext);
// Reset the transform matrix so the time and FPS does not pan or zoom m_d2dContext->SetTransform(m_orientationTransform2D);
// Set up the string to print:
std::wstring s = std::wstring(
L"Total Time: ") + std::to_wstring(m_timeTotal) + std::wstring(L" FPS: ") + std::to_wstring(
(int)(0.5f+1.0f/m_timeDelta)); // FPS rounded to nearest int // Render the string in the top left corner
m_d2dContext->DrawText(s.c_str(), s.length(), m_textFormat.Get(), D2D1::RectF(0, 0, 600, 32), m_blackBrush.Get());
// Ignore D2DERR_RECREATE_TARGET error
void DirectXPage::OnRendering(Object^ sender, Object^ args) { // if (m_renderNeeded)
{
m_timer->Update();
m_renderer->Update(m_timer->Total, m_timer->Delta);
Now when you run the application it should update continuously. The FPS is a little difficult to read when it updates many times per second. We can slow it down and update the counter only every 16 frames by adding a static counter and "if" condition to the GraphRenderer's update.
Tip: Use Boolean operations and bit shifting instead of integer division wherever you can. The CPU needs to run the update method very quickly. It is best to minimize division, modulus, square roots, trigonometry, and all other complex functions in the update and render methods. Some of these complex functions are hundreds of times slower to execute than simple Boolean instructions. The optimizing compiler is most likely smart enough to realize that "x%16" is the same as "x&15" but every compiler is different and it may be best not to trust the compiler when there is a simple optimization such as this.
m_renderer->Render();
m_renderer->Present();
m_renderNeeded = false;
} }
void GraphRenderer::Update(float timeTotal, float timeDelta) { static int fpsCounter = -1;// Start at -1 so frame 0 updates timers fpsCounter++;
if((fpsCounter & 15) == 0) { // Update every 16 frames // Record the times for display in the render method:
m_timeDelta = timeDelta;
m_timeTotal = timeTotal;
} }