The Hammer Game Engine implements a comprehensive DPI-aware font rendering system that automatically detects display pixel density and scales fonts accordingly, ensuring crisp, professional text rendering across all display types - from standard monitors to 4K/Retina displays.
- GameEngine: DPI detection and centralized scale management
- FontManager: High-quality font loading and rendering with automatic sizing
- UIManager: DPI-aware UI scaling and text positioning
Display Detection → DPI Calculation → Font Sizing → Quality Rendering → UI Integration
(GameEngine) (GameEngine) (FontManager) (FontManager) (UIManager)
During initialization, GameEngine automatically detects display characteristics:
// Automatic DPI detection in GameEngine::init()
int pixelWidth, pixelHeight;
int logicalWidth, logicalHeight;
SDL_GetWindowSizeInPixels(mp_window.get(), &pixelWidth, &pixelHeight);
SDL_GetWindowSize(mp_window.get(), &logicalWidth, &logicalHeight);
float scaleX = static_cast<float>(pixelWidth) / static_cast<float>(logicalWidth);
float scaleY = static_cast<float>(pixelHeight) / static_cast<float>(logicalHeight);
float dpiScale = std::max(scaleX, scaleY);Base font sizes are automatically scaled and pixel-aligned for crisp rendering:
// DPI-aware font sizing with pixel alignment
int baseFontSize = static_cast<int>(std::round(24.0f * dpiScale / 2.0f) * 2.0f);
int uiFontSize = static_cast<int>(std::round(18.0f * dpiScale / 2.0f) * 2.0f);
// Ensure minimum readable sizes
baseFontSize = std::max(baseFontSize, 12);
uiFontSize = std::max(uiFontSize, 10);Key Features:
- Pixel Alignment: Sizes rounded to even numbers for crisp rendering
- Minimum Constraints: Ensures text remains readable on all displays
- Proportional Scaling: Maintains relative size relationships across display types
FontManager loads fonts with professional quality settings:
// Automatic quality configuration
TTF_SetFontHinting(font.get(), TTF_HINTING_NORMAL); // Better outline quality
TTF_SetFontKerning(font.get(), 1); // Proper character spacing
TTF_SetFontStyle(font.get(), TTF_STYLE_NORMAL); // Consistent rendering// Blended rendering for high-quality anti-aliasing
auto surface = TTF_RenderText_Blended(font, text.c_str(), 0, color);
// Linear texture scaling for smooth rendering
SDL_SetTextureScaleMode(texture.get(), SDL_SCALEMODE_LINEAR);
// Pixel-perfect positioning
SDL_FRect dstRect = {
std::roundf(x - width/2.0f), // Rounded to pixel boundaries
std::roundf(y - height/2.0f),
static_cast<float>(width),
static_cast<float>(height)
};- Scale Factor: 1.0x
- Base Font: 24px → 24px
- UI Font: 18px → 18px
- Optimization: Performance-focused with crisp rendering
- Scale Factor: 1.5-2.0x
- Base Font: 24px → 36-48px
- UI Font: 18px → 27-36px
- Optimization: Balanced quality and performance
- Scale Factor: 2.0-3.0x+
- Base Font: 24px → 48-72px+
- UI Font: 18px → 36-54px+
- Optimization: Maximum quality for premium displays
The DPI system works seamlessly with the UI system without requiring code changes:
// All UIManager components automatically use DPI-scaled fonts
auto& ui = UIManager::Instance();
// Button text automatically crisp on all displays
ui.createButton("play_btn", {300, 200, 0, 0}, "Play Game"); // Auto-sizes with DPI scaling
// Labels use DPI-scaled fonts and positioning
ui.createLabel("score", {20, 20, 0, 0}, "Score: 0"); // Text size scales with display// FontManager automatically uses DPI-scaled fonts
FontManager::Instance().drawText(
"Hello World",
"fonts_Arial", // Automatically DPI-scaled font
400, 300, // Position adjusted by UIManager
{255, 255, 255, 255},
renderer
);The DPI system integrates seamlessly with the auto-sizing system:
// Auto-sizing works correctly across all DPI levels
ui.createLabel("dynamic", {x, y, 0, 0}, "Dynamic Content");
// - Text measured using DPI-appropriate font
// - Component sized using accurate measurements
// - Rendering crisp on all display types// SDL3 window configuration for DPI awareness
int flags = SDL_WINDOW_HIGH_PIXEL_DENSITY; // Enable high-DPI support
mp_window.reset(SDL_CreateWindow(title, width, height, flags));
// SDL3 rendering hints for quality
SDL_SetHint(SDL_HINT_RENDER_LINE_METHOD, "3"); // Geometry-based line renderingThe system automatically configures optimal rendering settings:
- Blended Alpha: High-quality anti-aliasing
- Linear Filtering: Smooth texture scaling
- Pixel Alignment: Eliminates sub-pixel blurriness
- Proper Blend Modes: Preserves alpha channels for crisp edges
- One-Time Calculation: DPI detected once at startup
- Pre-Calculated Sizes: Font sizes determined during initialization
- Smart Pointer Management: Efficient memory handling
- Thread-Safe Design: Concurrent access support
// DPI calculation before thread spawning
float dpiScale = calculateDPIScale();
// Thread-safe font loading with DPI-scaled sizes
FontManager& fontMgr = FontManager::Instance();
fontMgr.loadFont("res/fonts", "fonts_Arial", scaledFontSize);class GamePlayState : public GameState {
public:
void render() override {
auto& fontManager = FontManager::Instance();
auto& gameEngine = GameEngine::Instance();
// Text automatically scaled for current display
fontManager.drawText(
"Score: 1000",
"fonts_Arial", // DPI-appropriate font
gameEngine.getLogicalWidth() / 2, // Position scaled by UIManager
50,
{255, 255, 255, 255},
gameEngine.getRenderer()
);
}
};void updatePlayerStats() {
auto& ui = UIManager::Instance();
// Text automatically uses appropriate font size for display
std::string healthText = "Health: " + std::to_string(player.getHealth());
ui.setText("hud_health", healthText); // DPI-scaled font used automatically
// Auto-sizing works correctly with DPI scaling
ui.calculateOptimalSize("hud_health"); // Measures with DPI-appropriate font
}// Use consistent base sizes that scale well
const int BASE_FONT_SIZE = 24; // Scales to 24/36/48+ based on DPI
const int UI_FONT_SIZE = 18; // Scales to 18/27/36+ based on DPI
const int SMALL_FONT_SIZE = 14; // For captions and details// Design components to work across scale factors
ui.createButton("action", {x, y, 0, 0}, "Click Me"); // Auto-sizes with DPI scaling
ui.createTitle("header", {0, y, windowWidth, 0}, "Game Title"); // Centers with DPI scaling- Test on standard DPI monitors (96-120 DPI)
- Test on high DPI displays (150-200 DPI)
- Test on 4K/Retina displays (250+ DPI)
- Verify text remains crisp and readable at all scales
- Check that UI elements scale proportionally
Blurry Text:
- Ensure SDL3 window uses
SDL_WINDOW_HIGH_PIXEL_DENSITYflag - Verify DPI detection is working correctly
- Check that coordinate rounding is enabled in rendering
Incorrect Scaling:
- Verify both pixel and logical window sizes are detected correctly
- Ensure DPI scale is calculated before font loading
- Check that FontManager is using DPI-scaled font sizes
Performance Issues:
- Pre-load fonts at startup, not during rendering
- Use cached textures for frequently changing text
- Verify DPI calculation only happens once at initialization
// Check DPI detection results
float dpiScale = GameEngine::Instance().getDPIScale();
int windowWidth = GameEngine::Instance().getLogicalWidth();
int windowHeight = GameEngine::Instance().getLogicalHeight();
std::cout << "DPI Scale: " << dpiScale << std::endl;
std::cout << "Logical Size: " << windowWidth << "x" << windowHeight << std::endl;
// Verify font loading
bool fontLoaded = FontManager::Instance().isFontLoaded("fonts_Arial");
std::cout << "Font loaded: " << (fontLoaded ? "Yes" : "No") << std::endl;float getDPIScale() const; // Get calculated DPI scale factor
SDL_Window* getWindow() const; // Access window for DPI calculations
SDL_Renderer* getRenderer() const; // Access renderer for text rendering// All existing methods automatically use DPI-scaled fonts
bool loadFont(const std::string& fontFile, const std::string& fontID, int fontSize);
void drawText(const std::string& text, const std::string& fontID, int x, int y,
SDL_Color color, SDL_Renderer* renderer);
bool measureText(const std::string& text, const std::string& fontID, int* width, int* height);
bool measureMultilineText(const std::string& text, const std::string& fontID,
int maxWidth, int* width, int* height);float getGlobalScale() const; // Get current UI scale factor
void setGlobalScale(float scale); // Manually adjust UI scaling (rarely needed)This DPI system works seamlessly with:
- Auto-Sizing System: Content measurement uses DPI-scaled fonts
- UIManager: All UI components automatically use DPI scaling
- SDL3 Logical Presentation: Compatible with all presentation modes
The DPI-aware font system ensures that text remains crisp and readable across all display types while maintaining consistent visual proportions and professional appearance.