Windows (DirectX 12) |
Linux (Vulkan) |
MacOS (Metal) |
iOS (Metal) |
---|---|---|---|
This tutorial demonstrates animated text rendering with dynamic font atlas updates using Methane UI. Three colored text blocks are animated with continuous characters typing. Each text block is rendered as a single mesh displaying character glyphs from the font atlas texture to screen rectangles, which are generated on CPU using Freetype 2.0 library.
Font atlas texture can be updated dynamically by adding new character glyphs on demand, as the user types text including any non-Ascii character sets. Text characters layout and mesh generation is done on CPU using Methane implementation, without using 3rd-party libraries and supports horizontal and vertical text alignment in rectangular areas with wrapping by characters and words. Right-to-left and Arabic language characters layout is not supported yet.
Keyboard actions are enabled with TypographyAppController class derived from Platform::Input::Keyboard::ActionControllerBase:
Typography App Action | Keyboard Shortcut |
---|---|
Switch Text Wrap Mode (None, Anywhere, Word) | W |
Switch Text Horizontal Alignment (Left, Right, Center, Justify) | H |
Switch Text Vertical Alignment (Top, Bottom, Center) | V |
Switch Incremental Text Update | U |
Switch Typing Direction (Forward, Backward) | D |
Speedup Typing | + |
Slowdown Typing | - |
Common keyboard controls are enabled by the Platform
, Graphics
and UserInterface
application controllers:
- Methane::Platform::AppController
- Methane::Graphics::AppController, AppContextController
- Methane::UserInterface::AppController
TypographyApp
class is declared in header file TypographyApp.h,
and is derived from UserInterface::App base class, same as in previous tutorial.
Base application class UserInterface::App<TypographyFrame>
is using frame structure TypographyFrame
, which defines only
render command list and execution command list set which wraps this command list.
TypographyApp
class defines settings structure TypographyApp::Settings
, getter of the current settings
TypographyApp::GetSettings
and individual setters for each application setting:
TypographyApp::SetTextLayout
- allows to change word wrapping mode, horizontal and vertical text layouts for all text blocks;TypographyApp::SetForwardTypingDirection
- changes text typing animation: appending new characters iftrue
, or backspace deleting iffalse
;TypographyApp::SetTextUpdateInterval
- changes text typing time interval in milliseconds;TypographyApp::SetIncrementalTextUpdate
- enables or disables incremental updating of text block mesh buffers.
Application setting getters can be changed by user in runtime with keyboard shortcuts, handled in TypographyAppController.h.
TypographyApp
class contains the following private members:
gui::Font
objects each one for unique font, size and color;gui::TextItem
objects each for one text block;gui::Badge
objects for rendering font atlas textures on screen;std::vector<size_t>
displayed lengths of text in each text block incremented with animation;Timer::TimeDuration
holds duration of the last text block update to be displayed on screen.
#pragma once
#include <Methane/Kit.h>
#include <Methane/Data/Receiver.hpp>
namespace Methane::Tutorials
{
namespace gfx = Methane::Graphics;
namespace rhi = Methane::Graphics::Rhi;
namespace gui = Methane::UserInterface;
struct TypographyFrame final : gfx::AppFrame
{
rhi::RenderCommandList render_cmd_list;
rhi::CommandListSet execute_cmd_list_set;
using gfx::AppFrame::AppFrame;
};
using UserInterfaceApp = UserInterface::App<TypographyFrame>;
class TypographyApp final
: public UserInterfaceApp
, private Data::Receiver<gui::IFontLibraryCallback>
, private Data::Receiver<gui::IFontCallback>
{
public:
struct Settings
{
gui::Text::Layout text_layout { gui::Text::Wrap::Word, gui::Text::HorizontalAlignment::Center, gui::Text::VerticalAlignment::Top };
bool is_incremental_text_update = true;
bool is_forward_typing_direction = true;
double typing_update_interval_sec = 0.03;
};
TypographyApp();
~TypographyApp() override;
// GraphicsApp overrides
...
// UserInterface::App overrides
std::string GetParametersString() override;
// Settings accessors
const Settings& GetSettings() const noexcept { return m_settings; }
void SetTextLayout(const gui::Text::Layout& text_layout);
void SetForwardTypingDirection(bool is_forward_typing_direction);
void SetTextUpdateInterval(double text_update_interval_sec);
void SetIncrementalTextUpdate(bool is_incremental_text_update);
private:
...
Settings m_settings;
gui::FontContext m_font_context;
std::vector<gui::Font> m_fonts;
Ptrs<gui::TextItem> m_texts;
Ptrs<gui::Badge> m_font_atlas_badges;
std::vector<size_t> m_displayed_text_lengths;
double m_text_update_elapsed_sec = 0.0;
Timer::TimeDuration m_text_update_duration;
};
} // namespace Methane::Tutorials
Fonts and text blocks are initialized in TypographyApp::Init()
method in a for
loop for each block index.
Text blocks are positioned one below another with vertical_text_pos_in_dots
variable which holds vertical
position in DPI-independent Dot units.
const gfx::FrameSize frame_size_in_dots = GetFrameSizeInDots();
const uint32_t frame_width_without_margins = frame_size_in_dots.GetWidth() - 2 * g_margin_size_in_dots;
int32_t vertical_text_pos_in_dots = g_top_text_pos_in_dots;
for(size_t block_index = 0; block_index < g_text_blocks_count; ++block_index)
{
const FontSettings& font_settings = g_font_settings[block_index];
const size_t displayed_text_length = m_displayed_text_lengths[block_index];
const std::u32string displayed_text_block = g_text_blocks[block_index].substr(0, displayed_text_length);
// Add font to library
m_fonts.push_back( ... );
// Add text element
m_texts.push_back( ... );
vertical_text_pos_in_dots = m_texts.back()->GetRectInDots().GetBottom() + g_margin_size_in_dots;
}
Font objects are used to create and render text blocks on screen. Font objects are loaded from Data::FontProvider
singleton
with specified font settings and are added to fonts library. Data::FontProvider
loads TTF
fonts from application
resources. Font settings include:
- Font description:
- Font unique name for registration in fonts library
- Font file path loaded with data provider
- Font size in points
- Font resolution DPI
- Initial alphabet to be rendered and added to font atlas (it is also dynamically extended on demand)
UserInterface::FontContext
is a helper class which holds references of FontLibrary
and Data::IProvider
to facilitate font creation and queries via FontContext::GetFont
method.
// Add font to library
m_fonts.push_back(
m_font_context.GetFont(
gui::Font::Settings
{
font_settings.desc,
GetUIContext().GetFontResolutionDpi(),
gui::Font::GetAlphabetFromText(displayed_text_block)
}
)
);
Each text block object is created using UserInterface::Context
object acquired with UserInterface::App::GetUIContext()
method, font object which was created previously and text settings:
- Font name registered in fonts library
- Text string in UTF8 or UTF32 format
- Rectangle area in Dots or Pixels on screen for fit the text in
- Layout describing how the text will be fit into the rectangular area
- Wrapping mode (No wrapping, Wrap anywhere, Wrap on word boundaries)
- Horizontal alignment (Left, Right, Center)
- Vertical alignment (Top, Bottom, Center)
- Color of text block
- Incremental text mesh update flag
// Add text element
m_texts.push_back(
std::make_shared<gui::TextItem>(GetUIContext(), m_fonts.back(),
gui::Text::SettingsUtf32
{
font_settings.desc.name,
displayed_text_block,
gui::UnitRect
{
gui::Units::Dots,
gfx::Point2I { g_margin_size_in_dots, vertical_text_pos_in_dots },
gfx::FrameSize { frame_width_without_margins, 0U /* calculated height */ }
},
m_settings.text_layout,
gfx::Color4F(font_settings.color, 1.F),
m_settings.is_incremental_text_update
}
)
);
Font objects hold a font atlas textures which are displayed on screen in this tutorial using UserInterface::Badge
objects,
created in TypographyTutorial::UpdateFontAtlasBadges()
method:
oid TypographyApp::UpdateFontAtlasBadges()
{
const std::vector<gui::Font> fonts = m_font_context.GetFontLibrary().GetFonts();
const rhi::RenderContext& context = GetRenderContext();
// Remove obsolete font atlas badges
...
// Add new font atlas badges
for(const gui::Font& font : fonts)
{
const rhi::Texture& font_atlas_texture = font.GetAtlasTexture(context);
if (!font_atlas_texture.IsInitialized() ||
std::any_of(m_font_atlas_badges.begin(), m_font_atlas_badges.end(),
[&font_atlas_texture](const Ptr<gui::Badge>& font_atlas_badge_ptr)
{
return font_atlas_badge_ptr->GetTexture() == font_atlas_texture;
}))
continue;
m_font_atlas_badges.emplace_back(CreateFontAtlasBadge(font, font_atlas_texture));
}
LayoutFontAtlasBadges(GetRenderContext().GetSettings().frame_size);
}
Font atlas badge bound to atlas texture is created with helper method TypographyApp::CreateFontAtlasBadge
:
Ptr<gui::Badge> TypographyApp::CreateFontAtlasBadge(const gui::Font& font, const rhi::Texture& atlas_texture)
{
const auto font_color_by_name_it = g_font_color_by_name.find(font.GetSettings().description.name);
const gui::Color3F& font_color = font_color_by_name_it != g_font_color_by_name.end()
? font_color_by_name_it->second : g_misc_font_color;
return std::make_shared<gui::Badge>(
GetUIContext(), atlas_texture,
gui::Badge::Settings
{
font.GetSettings().description.name + " Font Atlas",
gui::Badge::FrameCorner::BottomLeft,
gui::UnitSize(gui::Units::Pixels, static_cast<const gfx::FrameSize&>(atlas_texture.GetSettings().dimensions)),
gui::UnitSize(gui::Units::Dots, 16U, 16U),
gui::Color4F(font_color, 0.5F),
gui::Badge::TextureMode::RFloatToAlpha,
}
);
}
TypographyApp::LayoutFontAtlasBadges
is positioning all created atlas badges in the left-bottom corner of the screen
located one after another starting with largest textures and ending with smallest.
Animation function bound to time-animation in constructor of TypographyApp
class is called automatically as a part of
every render cycle, just before App::Update
function call.
TypographyApp::Animate
function updates text displayed in text blocks by adding or deleting characters and updates
text block positions once in a equal time spans.
TypographyApp::TypographyApp()
{
...
GetAnimations().emplace_back(std::make_shared<Data::TimeAnimation>(std::bind(&ShadowCubeApp::Animate, this, std::placeholders::_1, std::placeholders::_2)));
}
bool TypographyApp::Animate(double elapsed_seconds, double)
{
if (elapsed_seconds - m_text_update_elapsed_sec < m_settings.typing_update_interval_sec)
return true;
m_text_update_elapsed_sec = elapsed_seconds;
int32_t vertical_text_pos_in_dots = g_top_text_pos_in_dots;
for(size_t block_index = 0; block_index < g_text_blocks_count; ++block_index)
{
AnimateTextBlock(block_index, vertical_text_pos_in_dots);
}
UpdateParametersText();
return true;
}
Each text rendering object is managing its own set of GPU resources for each frame in swap-chain:
- Vertex and index buffers
- Shader uniforms buffer
- Font atlas texture
- Program bindings
To update text resources on GPU to make them ready for frame rendering,
Text::Update(...)
method is called for each text object in TypographyApp::Update()
.
bool TypographyApp::Update()
{
if (!UserInterfaceApp::Update())
return false;
// Update text block resources
for(const Ptr<gui::TextItem>& text_ptr : m_texts)
{
text_ptr->Update(GetFrameSize());
}
return true;
}
After updating text resources on GPU, the rendering is simple: UserInterface::Text::Draw(...)
is called for each text object
with per-frame render command list and debug group. Font atlas badges are rendered in the same way UserInterface::Badge::Draw(...)
.
bool TypographyApp::Render()
{
if (!UserInterfaceApp::Render())
return false;
const TypographyFrame& frame = GetCurrentFrame();
// Draw text blocks
META_DEBUG_GROUP_VAR(s_text_debug_group, "Text Blocks Rendering");
for(const Ptr<gui::TextItem>& text_ptr : m_texts)
{
text_ptr->Draw(frame.render_cmd_list, &s_text_debug_group);
}
// Draw font atlas badges
META_DEBUG_GROUP_VAR(s_atlas_debug_group, "Font Atlases Rendering");
for(const Ptr<gui::Badge>& badge_atlas_ptr : m_font_atlas_badges)
{
badge_atlas_ptr->Draw(frame.render_cmd_list, &s_atlas_debug_group);
}
RenderOverlay(frame.render_cmd_list);
frame.render_cmd_list.Commit();
// Execute command list on render queue and present frame to screen
GetRenderContext().GetRenderCommandKit().GetQueue().Execute(frame.execute_cmd_list_set);
GetRenderContext().Present();
return true;
}
TTF
fonts are added to the application embedded resources with add_methane_embedded_fonts
cmake-function.
include(MethaneApplications)
add_methane_application(
TARGET MethaneTypography
NAME "Methane Typography"
DESCRIPTION "Tutorial demonstrating dynamic text rendering and font atlases management with Methane Kit."
INSTALL_DIR "Apps"
SOURCES
TypographyApp.h
TypographyApp.cpp
TypographyAppController.h
TypographyAppController.cpp
)
set(FONTS
${RESOURCES_DIR}/Fonts/Roboto/Roboto-Regular.ttf
${RESOURCES_DIR}/Fonts/Playball/Playball-Regular.ttf
${RESOURCES_DIR}/Fonts/SawarabiMincho/SawarabiMincho-Regular.ttf
)
add_methane_embedded_fonts(MethaneTypography "${RESOURCES_DIR}" "${FONTS}")
target_link_libraries(MethaneTypography
PRIVATE
MethaneAppsCommon
)
Continue learning Methane Graphics programming in the next tutorial CubeMapArray, which is demonstrating cube-map array texturing and sky-box rendering.