-
Notifications
You must be signed in to change notification settings - Fork 409
Drawing text
This lesson covers drawing text using bitmap fonts and the sprite renderer.
For a DirectX 12 application, you can also rely on Direct2D/DirectWrite being available which is recommended for true vector-font features such as high quality across a wide range of scales, for complex layouts, or large-alphabet fonts. SpriteFont is intended for low-overhead bitmap-font rendering using a font that can be captured to a single texture.
UWP on the Xbox does support Direct2D/DirectWrite, but Microsoft GDKX and Xbox One XDK do not.
First create a new project using the instructions from the earlier lessons: Using DeviceResources and Adding the DirectX Tool Kit which we will use for this lesson.
The text renderer in DirectX Tool Kit makes use of bitmap fonts, so the first step is to create a .spritefont
file by capturing a system TrueType font using the makespritefont command-line utility.
- Download the MakeSpriteFont.exe from the DirectX Tool Kit site save the EXE into your project's folder.
- Open a command-prompt and then change to your project's folder.
Run the following command-line
MakeSpriteFont "Courier New" myfile.spritefont /FontSize:32
Then from the top menu in Visual Studio select Project / Add Existing Item.... Select myfile.spritefont and click "OK".
If you are using a Universal Windows Platform (UWP) app or Xbox One project rather than a Windows desktop app, you need to manually edit the Visual Studio project properties on the
myfile.spritefont
file and make sure "Content" is set to "Yes" so the data file will be included in your packaged build.
To get a Bold version of the font, run the following command-line:
MakeSpriteFont "Courier New" myfileb.spritefont /FontSize:32 /FontStyle:Bold
For an Italic version of the font, run the following command-line:
MakeSpriteFont "Courier New" myfilei.spritefont /FontSize:32 /FontStyle:Italic
For a Strikeout version of the font, run the following command-line:
MakeSpriteFont "Courier New" myfiles.spritefont /FontSize:32 /FontStyle:Strikeout
For a Underline version of the font, run the following command-line:
MakeSpriteFont "Courier New" myfileu.spritefont /FontSize:32 /FontStyle:Underline
In the Game.h file, add the following variable to the bottom of the Game class's private declarations (right after the m_graphicsMemory
variable you already added as part of setup):
std::unique_ptr<DirectX::DescriptorHeap> m_resourceDescriptors;
std::unique_ptr<DirectX::SpriteFont> m_font;
enum Descriptors
{
MyFont,
Count
};
In Game.cpp, add to the TODO of CreateDeviceDependentResources after where you have created m_graphicsMemory
:
m_resourceDescriptors = std::make_unique<DescriptorHeap>(device,
Descriptors::Count);
ResourceUploadBatch resourceUpload(device);
resourceUpload.Begin();
m_font = std::make_unique<SpriteFont>(device, resourceUpload,
L"myfile.spritefont",
m_resourceDescriptors->GetCpuHandle(Descriptors::MyFont),
m_resourceDescriptors->GetGpuHandle(Descriptors::MyFont));
auto uploadResourcesFinished = resourceUpload.End(
m_deviceResources->GetCommandQueue());
uploadResourcesFinished.wait();
In Game.cpp, add to the TODO of OnDeviceLost where you added m_graphicsMemory.reset()
:
m_font.reset();
m_resourceDescriptors.reset();
Build and run the application which will still not be displaying anything but the cornflower blue window, but will have a font loaded.
Troubleshooting: If you get a runtime exception, then you may have the
myfile.spritefont
in the wrong folder, have modified the "Working Directory" in the "Debugging" configuration settings, or otherwise changed the expected paths at runtime of the application. You should set a break-point onstd::make_unique<SpriteFont>(...)
and step into the code to find the exact problem.
In the Game.h file, add the following variables to the bottom of the Game class's private declarations:
std::unique_ptr<DirectX::SpriteBatch> m_spriteBatch;
DirectX::SimpleMath::Vector2 m_fontPos;
In Game.cpp, modify TODO of CreateDeviceDependentResources to include after resourceUpload.Begin
and before the resourceUpload.End
:
RenderTargetState rtState(m_deviceResources->GetBackBufferFormat(),
m_deviceResources->GetDepthBufferFormat());
SpriteBatchPipelineStateDescription pd(rtState);
m_spriteBatch = std::make_unique<SpriteBatch>(device, resourceUpload, pd);
In Game.cpp, add to the TODO of CreateWindowSizeDependentResources:
auto viewport = m_deviceResources->GetScreenViewport();
m_spriteBatch->SetViewport(viewport);
auto size = m_deviceResources->GetOutputSize();
m_fontPos.x = float(size.right) / 2.f;
m_fontPos.y = float(size.bottom) / 2.f;
If using the UWP template, you also need to add
m_spriteBatch->SetRotation(m_deviceResources->GetRotation());
to handle display orientation changes.
In Game.cpp, add to the TODO of OnDeviceLost:
m_spriteBatch.reset();
In Game.cpp, modify the TODO section of Render to be:
ID3D12DescriptorHeap* heaps[] = { m_resourceDescriptors->Heap() };
commandList->SetDescriptorHeaps(static_cast<UINT>(std::size(heaps)), heaps);
m_spriteBatch->Begin(commandList);
const wchar_t* output = L"Hello World";
Vector2 origin = m_font->MeasureString(output) / 2.f;
m_font->DrawString(m_spriteBatch.get(), output,
m_fontPos, Colors::White, 0.f, origin);
m_spriteBatch->End();
Build and run to see our text string centered in the middle of the rendering window:
Note that setting the descriptor heap is left to the caller. This allows the application to use their own heaps instead of the
DescriptorHeap
class for where the font texture is in memory. You can freely mix and match heaps in the application, but remember that you can have only a single texture descriptor heap and a single sampler descriptor heap active at any given time.
In pch.h after the other #include
statements, add:
#include <string>
In Game.cpp, modify the TODO section of Render to be:
std::wstring output = std::wstring(L"Hello") + std::wstring(L" World");
ID3D12DescriptorHeap* heaps[] = { m_resourceDescriptors->Heap() };
commandList->SetDescriptorHeaps(static_cast<UINT>(std::size(heaps)), heaps);
m_spriteBatch->Begin(commandList);
Vector2 origin = m_font->MeasureString(output.c_str()) / 2.f;
m_font->DrawString(m_spriteBatch.get(), output.c_str(),
m_fontPos, Colors::White, 0.f, origin);
m_spriteBatch->End();
Build and run to see our text string centered in the middle of the rendering window.
In pch.h after the other #include
statements, add:
#include <codecvt>
#include <locale>
In Game.cpp, modify the TODO section of Render to be:
const char *ascii = "Hello World";
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
std::wstring output = converter.from_bytes(ascii);
ID3D12DescriptorHeap* heaps[] = { m_resourceDescriptors->Heap() };
commandList->SetDescriptorHeaps(static_cast<UINT>(std::size(heaps)), heaps);
m_spriteBatch->Begin(commandList);
Vector2 origin = m_font->MeasureString(output.c_str()) / 2.f;
m_font->DrawString(m_spriteBatch.get(), output.c_str(),
m_fontPos, Colors::White, 0.f, origin);
m_spriteBatch->End();
Build and run to see our text string centered in the middle of the rendering window.
In Game.cpp, modify the TODO section of Render to be:
ID3D12DescriptorHeap* heaps[] = { m_resourceDescriptors->Heap() };
commandList->SetDescriptorHeaps(static_cast<UINT>(std::size(heaps)), heaps);
m_spriteBatch->Begin(commandList);
const wchar_t* output = L"Hello World";
Vector2 origin = m_font->MeasureString(output) / 2.f;
m_font->DrawString(m_spriteBatch.get(), output,
m_fontPos + Vector2(1.f, 1.f), Colors::Black, 0.f, origin);
m_font->DrawString(m_spriteBatch.get(), output,
m_fontPos + Vector2(-1.f, 1.f), Colors::Black, 0.f, origin);
m_font->DrawString(m_spriteBatch.get(), output,
m_fontPos, Colors::White, 0.f, origin);
m_spriteBatch->End();
Build and run to see our text string centered in the middle of the rendering window but with a drop-shadow:
In Game.cpp, modify the TODO section of Render to be:
ID3D12DescriptorHeap* heaps[] = { m_resourceDescriptors->Heap() };
commandList->SetDescriptorHeaps(static_cast<UINT>(std::size(heaps)), heaps);
m_spriteBatch->Begin(commandList);
const wchar_t* output = L"Hello World";
Vector2 origin = m_font->MeasureString(output) / 2.f;
m_font->DrawString(m_spriteBatch.get(), output,
m_fontPos + Vector2(1.f, 1.f), Colors::Black, 0.f, origin);
m_font->DrawString(m_spriteBatch.get(), output,
m_fontPos + Vector2(-1.f, 1.f), Colors::Black, 0.f, origin);
m_font->DrawString(m_spriteBatch.get(), output,
m_fontPos + Vector2(-1.f, -1.f), Colors::Black, 0.f, origin);
m_font->DrawString(m_spriteBatch.get(), output,
m_fontPos + Vector2(1.f, -1.f), Colors::Black, 0.f, origin);
m_font->DrawString(m_spriteBatch.get(), output,
m_fontPos, Colors::White, 0.f, origin);
m_spriteBatch->End();
Build and run to see our text string centered in the middle of the rendering window but with an outline:
-
If you are wanting to render text similar to how a classic 'console' application does, see TextConsole.
-
If you want to render Xbox controller button artwork composed with standard text, see ControllerFont.
-
If you want to make use of DirectWrite for vector-based fonts on Windows, see Microsoft Docs with details on interop with DirectX 12 here.
Next lesson: Simple rendering
DirectX Tool Kit docs SpriteBatch, SpriteFont, MakeSpriteFont
All content and source code for this package are subject to the terms of the MIT License.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.
- Universal Windows Platform apps
- Windows desktop apps
- Windows 11
- Windows 10
- Xbox One
- Xbox Series X|S
- x86
- x64
- ARM64
- Visual Studio 2022
- Visual Studio 2019 (16.11)
- clang/LLVM v12 - v18
- MinGW 12.2, 13.2
- CMake 3.20