Skip to content

Drawing text

Chuck Walbourn edited this page Jul 12, 2016 · 29 revisions

This lesson covers drawing text using bitmap fonts and the sprite renderer.

Setup

First create a new project using the instructions from the first two lessons: The basic game loop and Adding the DirectX Tool Kit which we will use for this lesson.

Creating a font

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

Loading a bitmap font

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::SpriteBatch> m_spriteBatch;

enum Descriptors
{
    MyFont,
    Count
};

In Game.cpp, add to the TODO of CreateDevice after where you have created m_graphicsMemory:

m_resourceDescriptors = std::make_unique<DescriptorHeap>(m_d3dDevice.Get(),
    D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
    D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE,
    Descriptors::Count);

ResourceUploadBatch resourceUpload(m_d3dDevice.Get());

resourceUpload.Begin();

m_font = std::make_unique<SpriteFont>(m_d3dDevice.Get(), resourceUpload,
    L"myfile.spritefont",
    m_resourceDescriptors->GetCpuHandle(Descriptors::MyFont),
    m_resourceDescriptors->GetGpuHandle(Descriptors::MyFont));

auto uploadResourcesFinished = resourceUpload.End(m_commandQueue.Get());

WaitForGpu();

uploadResourcesFinished.wait();

In Game.cpp, add to the TODO of OnDeviceLost:

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 on std::make_unique<SpriteFont>(...) and step into the code to find the exact problem.

Drawing text using a font

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 CreateDevice to include after resourceUpload.Begin and before the resourceUpload.End:

RenderTargetState rtState(DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_D32_FLOAT);

SpriteBatchPipelineStateDescription pd(rtState);
m_spriteBatch = std::make_unique<SpriteBatch>(m_d3dDevice.Get(), resourceUpload, pd);

In Game.cpp, add to the TODO of CreateResources:

D3D12_VIEWPORT viewport = { 0.0f, 0.0f,
    static_cast<float>(backBufferWidth), static_cast<float>(backBufferHeight),
    D3D12_MIN_DEPTH, D3D12_MAX_DEPTH };
m_spriteBatch->SetViewport(viewport);

m_fontPos.x = backBufferWidth / 2.f;
m_fontPos.y = backBufferHeight / 2.f;

If using the UWP template, you also need to add m_spriteBatch->SetRotation(m_outputRotation); 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() };
m_commandList->SetDescriptorHeaps(1, heaps);

m_spriteBatch->Begin(m_commandList.Get());

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:

Screenshot Hello World

Using std::wstring for text

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() };
m_commandList->SetDescriptorHeaps(1, heaps);

m_spriteBatch->Begin(m_commandList.Get());

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.

Using ASCII text

In pch.h after the other #include statements, add:

#include <locale>
#include <codecvt>

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() };
m_commandList->SetDescriptorHeaps(1, heaps);

m_spriteBatch->Begin(m_commandList.Get());

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.

Drop-shadow effect

In Game.cpp, modify the TODO section of Render to be:

ID3D12DescriptorHeap* heaps[] = { m_resourceDescriptors->Heap() };
m_commandList->SetDescriptorHeaps(1, heaps);

m_spriteBatch->Begin(m_commandList.Get());

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:

Screenshot Hello World with shadow

Outline effect

In Game.cpp, modify the TODO section of Render to be:

ID3D12DescriptorHeap* heaps[] = { m_resourceDescriptors->Heap() };
m_commandList->SetDescriptorHeaps(1, heaps);

m_spriteBatch->Begin(m_commandList.Get());

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:

Screenshot Hello World with outline

More to explore

  • 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.

Next lesson: Simple rendering

Further reading

DirectX Tool Kit docs SpriteBatch, SpriteFont, MakeSpriteFont

For Use

  • Universal Windows Platform apps
  • Windows desktop apps
  • Windows 11
  • Windows 10
  • Xbox One
  • Xbox Series X|S

Architecture

  • x86
  • x64
  • ARM64

For Development

  • Visual Studio 2022
  • Visual Studio 2019 (16.11)
  • clang/LLVM v12 - v18
  • MinGW 12.2, 13.2
  • CMake 3.20

Related Projects

DirectX Tool Kit for DirectX 11

DirectXMesh

DirectXTex

DirectXMath

Tools

Test Suite

Model Viewer

Content Exporter

DxCapsViewer

See also

DirectX Landing Page

Clone this wiki locally