diff --git a/src/graphics/font/FontManager.cpp b/src/graphics/font/FontManager.cpp index 29066e9..82b2a9e 100644 --- a/src/graphics/font/FontManager.cpp +++ b/src/graphics/font/FontManager.cpp @@ -1,44 +1,68 @@ + #include "FontManager.h" #include -FontManager::FontManager() { +FontManager::FontManager() { + // 构造函数:初始化字体管理器 + // m_fonts 映射表会在默认构造时初始化为空 } FontManager::~FontManager() { + // 析构函数:清理所有加载的字体资源 for (auto& pair : m_fonts) { + // 关闭并释放每个字体资源 TTF_CloseFont(pair.second); } - + // 清空字体缓存表 m_fonts.clear(); - - } TTF_Font* FontManager::loadFont(const std::string& fontID, int ptSize) { + // 构造字体文件的完整路径 std::string path = "assets/fonts/" + fontID; - std::string key = fontID + std::to_string(ptSize); + // 计算该字体在缓存中的唯一哈希键 + size_t key = makeHash(fontID, ptSize); + + // 检查字体是否已经被加载过 auto it = m_fonts.find(key); - // 检查字体是否已经加载 if (it != m_fonts.end()) { + // 字体已缓存,直接返回缓存的字体指针 return it->second; } - - TTF_Font* font = TTF_OpenFont(path.c_str(), ptSize); + + // 从文件中加载字体 + TTF_Font* font = TTF_OpenFont(path.c_str(), ptSize); if (!font) { - throw std::runtime_error("无法加载字体: " + key); + // 字体加载失败,抛出异常 + throw std::runtime_error("无法加载字体: " + fontID); } + + // 将新加载的字体存入缓存 m_fonts[key] = font; return font; } -TTF_Font* FontManager::getFont(const std::string& key, int ptSize) { +TTF_Font* FontManager::getFont(const std::string& fontID, int ptSize) { + // 计算该字体的哈希键 + size_t key = makeHash(fontID, ptSize); + + // 从字体缓存中查找该键对应的字体 auto it = m_fonts.find(key); if (it != m_fonts.end()) { + // 字体已缓存,直接返回缓存的字体指针 return it->second; } - return loadFont(key, ptSize);; + // 如果缓存中未找到,自动调用loadFont加载字体 + return loadFont(fontID, ptSize); } +size_t FontManager::makeHash(const std::string& fontID, int ptSize) { + // 分别计算字体名称和大小的哈希值 + size_t h1 = std::hash{}(fontID); + size_t h2 = std::hash{}(ptSize); - + // 组合两个哈希值:使用XOR运算和位左移组合两个独立的哈希值 + // 这样可以确保不同的fontID或ptSize组合都会产生不同的哈希值 + return h1 ^ (h2 << 1); +} diff --git a/src/graphics/font/FontManager.h b/src/graphics/font/FontManager.h index ddaaa58..90b45fd 100644 --- a/src/graphics/font/FontManager.h +++ b/src/graphics/font/FontManager.h @@ -1,26 +1,67 @@ +/** + * @file FontManager.h + * @brief 字体加载和管理的实现,使用SDL的ttf库扩展 + * @author zhenyan121 + * @date 2025-12-12 + */ #pragma once #include #include #include #include +/** + * @class FontManager + * @brief 字体管理的类 + * + * 用于加载字体,缓存字体 + * 提供接口,用与获取字体缓存 + */ class FontManager { public: FontManager(); + + /** + * @brief 析构函数 + * 清理已经加载的字体资源 + */ ~FontManager(); - // 加载字体(路径 + 字号) - // ptSize 为字号 + /** + * @brief 加载字体 + * 从字体文件中加载字体并将其缓存。不同大小的字体将被独立存储。 + * @param fontID 字体文件名称的引用,不包含路径前缀 + * @param ptSize 字体的磅值大小,不同的大小会产生不同的缓存键 + * @return 返回加载字体的指针,失败时抛出std::runtime_error异常 + * @note 该函数会自动检查字体是否已缓存,如已缓存则直接返回缓存的字体 + * @throw std::runtime_error 当字体文件无法打开时抛出异常 + */ TTF_Font* loadFont(const std::string& fontID, int ptSize); - - // 获取已加载的字体 - TTF_Font* getFont(const std::string& key, int ptSize); + /** + * @brief 获取字体 + * 获取指定字体的指针。如果字体未被加载过,将自动调用loadFont进行加载。 + * @param fontID 字体文件名称的引用,不包含路径前缀 + * @param ptSize 字体的磅值大小 + * @return 返回获取到的字体指针,保证非空;失败时会抛出异常 + * @note 该函数是获取字体的推荐方式,具有自动加载功能 + * @throw std::runtime_error 当字体加载失败时抛出异常 + */ + TTF_Font* getFont(const std::string& fontID, int ptSize); - + private: - // 用哈希表存储字体 - std::unordered_map m_fonts; + + std::unordered_map m_fonts; ///< 字体缓存表,使用哈希值作为键存储TTF_Font指针 + /** + * @brief 计算组合哈希值 + * 根据字体名称和大小计算唯一的哈希键用于缓存。采用黄金比例乘法混合算法。 + * @param fontID 字体文件名称的引用 + * @param ptSize 字体的磅值大小 + * @return 返回计算得到的哈希值,作为m_fonts字典的键 + * @note 内部实现使用XOR运算和位移组合两个独立的哈希值 + */ + size_t makeHash(const std::string& fontID, int ptSize); }; \ No newline at end of file diff --git a/src/graphics/font/TextRenderer.cpp b/src/graphics/font/TextRenderer.cpp index b3e0290..d1a3573 100644 --- a/src/graphics/font/TextRenderer.cpp +++ b/src/graphics/font/TextRenderer.cpp @@ -1,5 +1,6 @@ #include "TextRenderer.h" - +#include +#include TextRenderer::TextRenderer(SDL_Renderer* renderer, FontManager* fontManager) : m_fontManager(fontManager), m_renderer(renderer) @@ -8,73 +9,105 @@ TextRenderer::TextRenderer(SDL_Renderer* renderer, FontManager* fontManager) : } TextRenderer::~TextRenderer() { - for (auto& pair : m_cache) { - - SDL_DestroyTexture(pair.second.texture); - - } - m_cache.clear(); + clearCache(); } - - -void TextRenderer::renderText(const std::string& text, TextStyle style, int x, int y) { - auto key = style.hash(); +std::pair TextRenderer::getTextSize(const std::string& text, TextStyle style) { + auto key = makeHash(text, style); auto it = m_cache.find(key); // 查找缓存 if (it != m_cache.end()) { + // 更新最后访问时间 + it->second.lastAccessTime = std::time(nullptr); + return {it->second.width, it->second.height}; + } + + // 创建并缓存纹理 + CachedText cached = createAndCacheTexture(text, style); + if (!cached.texture) { + return {0, 0}; + } + + return {cached.width, cached.height}; +} + +void TextRenderer::renderText(const std::string& text, TextStyle style, int x, int y) { + auto key = makeHash(text, style); + auto it = m_cache.find(key); + + // 查找缓存 + if (it != m_cache.end()) { + // 更新最后访问时间 + it->second.lastAccessTime = std::time(nullptr); + // 使用缓存的纹理, SDL_FRect为浮点数矩形 - SDL_FRect dest = { static_cast(x), static_cast(y), - static_cast(it->second.width), - static_cast(it->second.height) }; + SDL_FRect dest = { static_cast(x), static_cast(y), + static_cast(it->second.width), + static_cast(it->second.height) }; - // 绘制材质 NULL 的含义:绘制整个纹理(从 (0,0) 到纹理的完整宽高) &dest 目标区域 - SDL_RenderTexture(m_renderer, it->second.texture, NULL, &dest); - return; - } - - // 创建新的纹理 - TTF_Font* font = m_fontManager->getFont(style.fontID, style.fontSize); - if (!font) { - SDL_Log("错误:字体未找到 %s\n", style.fontID.c_str()); + // 绘制材质 NULL 的含义:绘制整个纹理(从 (0,0) 到纹理的完整宽高) &dest 目标区域 + SDL_RenderTexture(m_renderer, it->second.texture, NULL, &dest); return; } - // 创建文字表面 - SDL_Surface* surface = TTF_RenderText_Solid(font, text.c_str(),text.length(), style.color); - if (!surface) { - printf("错误:无法创建文字表面\n"); - return; - } - // 创建纹理 - SDL_Texture* texture = SDL_CreateTextureFromSurface(m_renderer, surface); - if (!texture) { - SDL_Log("错误:无法创建纹理\n"); - SDL_DestroySurface(surface); - return; - } - // 保存到缓存 - CachedText cached; - cached.texture = texture; - cached.width = surface->w; - cached.height = surface->h; - m_cache[key] = cached; + // 创建并缓存纹理 + CachedText cached = createAndCacheTexture(text, style); + if (!cached.texture) { + return; + } // 渲染 SDL_FRect dest = { static_cast(x), static_cast(y), - static_cast(surface->w), - static_cast(surface->h) }; - SDL_RenderTexture(m_renderer, texture, NULL, &dest); - - // 清理表面 - SDL_DestroySurface(surface); - - + static_cast(cached.width), + static_cast(cached.height) }; + SDL_RenderTexture(m_renderer, cached.texture, NULL, &dest); } -SDL_Texture* TextRenderer::createTextTexture(const std::string& text, const std::string& fontID, SDL_Color color) { - return nullptr; +TextRenderer::CachedText TextRenderer::createAndCacheTexture(const std::string& text, TextStyle style) { + CachedText result = {nullptr, 0, 0, std::time(nullptr)}; + + // 获取字体 + TTF_Font* font = m_fontManager->getFont(style.fontID, style.fontSize); + if (!font) { + SDL_Log("错误:字体未找到 %s\n", style.fontID.c_str()); + return result; + } + + // 创建文字表面 + SDL_Surface* surface = TTF_RenderText_Solid(font, text.c_str(),text.length(), style.color); + if (!surface) { + SDL_Log("错误:无法创建文字表面 '%s'\n", text.c_str()); + return result; + } + + // 创建纹理 + SDL_Texture* texture = SDL_CreateTextureFromSurface(m_renderer, surface); + int width = surface->w; + int height = surface->h; + SDL_DestroySurface(surface); // 立即释放表面 + + if (!texture) { + SDL_Log("错误:无法创建纹理\n"); + return result; + } + + // 保存结果 + result.texture = texture; + result.width = width; + result.height = height; + result.lastAccessTime = std::time(nullptr); + + // 保存到缓存 + auto key = makeHash(text, style); + m_cache[key] = result; + + // 检查是否需要清理缓存 + if (m_cache.size() > MAX_CACHE_SIZE) { + autoCleanCache(); + } + + return result; } @@ -85,3 +118,68 @@ std::string makeCacheKey(const std::string& text, const std::string& fontID, SDL std::to_string(color.b) + "-" + std::to_string(color.a); } + + + +size_t TextRenderer::makeHash(const std::string& text, TextStyle style) const { + // 使用组合哈希技术 + + size_t h1 = std::hash{}(style.fontID); + size_t h2 = std::hash{}(text); + size_t h3 = std::hash{}(style.fontSize); + size_t h4 = std::hash{}(style.color.r); + size_t h5 = std::hash{}(style.color.g); + size_t h6 = std::hash{}(style.color.b); + size_t h7 = std::hash{}(style.color.a); + + // 组合哈希(使用黄金比例乘法混合) + return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3) ^ (h5 << 4) ^ (h6 << 5) ^ (h7 << 6); + } + + +void TextRenderer::clearCache() { + for (auto& pair : m_cache) { + SDL_DestroyTexture(pair.second.texture); + } + m_cache.clear(); + SDL_Log("文本缓存已清空\n"); +} + + +void TextRenderer::autoCleanCache() { + // 如果缓存没有超过最大限制,则不进行清理 + if (m_cache.size() <= MAX_CACHE_SIZE) { + return; + } + + // 收集所有缓存项,按最后访问时间排序 + std::vector> cacheItems; + cacheItems.reserve(m_cache.size()); + + for (const auto& pair : m_cache) { + cacheItems.emplace_back(pair.first, pair.second.lastAccessTime); + } + + // 按最后访问时间从早到晚排序(最旧的在前) + std::sort(cacheItems.begin(), cacheItems.end(), + [](const auto& a, const auto& b) { + return a.second < b.second; + }); + + // 计算需要删除的数量 + size_t itemsToRemove = m_cache.size() - MIN_CACHE_SIZE; + + SDL_Log("缓存清理:删除 %zu / %zu 条目(当前缓存大小:%zu)\n", + itemsToRemove, m_cache.size(), m_cache.size()); + + // 删除最旧的缓存项 + for (size_t i = 0; i < itemsToRemove && i < cacheItems.size(); ++i) { + auto it = m_cache.find(cacheItems[i].first); + if (it != m_cache.end()) { + SDL_DestroyTexture(it->second.texture); + m_cache.erase(it); + } + } + + SDL_Log("缓存清理完成,当前缓存大小:%zu\n", m_cache.size()); +} \ No newline at end of file diff --git a/src/graphics/font/TextRenderer.h b/src/graphics/font/TextRenderer.h index d212895..c1af8ab 100644 --- a/src/graphics/font/TextRenderer.h +++ b/src/graphics/font/TextRenderer.h @@ -1,44 +1,167 @@ +/** + * @file TextRenderer.h + * @brief 文本渲染器类,用于将带有特定样式的文本渲染到屏幕上 + * @details 该类使用 SDL3 TTF 库进行文字渲染,并实现了纹理缓存机制以提高性能。 + * 支持获取文本尺寸和渲染文本到指定位置。 + * @author zhenyan121 + * @date 2025-12-12 + */ + #pragma once #include #include +#include +#include #include #include #include "Textstyle.h" #include "FontManager.h" +/** + * @class TextRenderer + * @brief 文本渲染器 + * @details 负责管理文本渲染的生命周期,包括: + * - 文本纹理的创建和缓存 + * - 文本尺寸的计算 + * - 文本到指定屏幕位置的渲染 + * + * 使用哈希缓存机制存储已渲染的文本纹理,避免重复创建相同的纹理。 + */ class TextRenderer { -private: - SDL_Renderer* m_renderer; - FontManager* m_fontManager; - - // 缓存文字纹理 - - - struct CachedText { - SDL_Texture* texture; - int width; - int height; - }; - - - - - std::unordered_map m_cache; - // 创建材质 - SDL_Texture* createTextTexture(const std::string& text, const std::string& fontID, SDL_Color color); - - - - - public: + /** + * @brief 构造函数 + * @param renderer SDL 渲染器指针,用于创建纹理和执行渲染操作。不可为 nullptr + * @param fontManager 字体管理器指针,用于获取指定字体 ID 和大小的字体对象。不可为 nullptr + * @details 初始化文本渲染器,设置必要的 SDL 渲染器和字体管理器引用 + */ TextRenderer(SDL_Renderer* renderer, FontManager* fontManager); + + /** + * @brief 析构函数 + * @details 自动释放所有缓存中的 SDL 纹理资源,防止内存泄漏 + */ ~TextRenderer(); + /** + * @brief 获取文本渲染后的尺寸 + * @param text 要计算尺寸的文本内容。支持任意 UTF-8 字符串 + * @param style 文本样式,包含字体 ID、大小、颜色等信息 + * @return 返回一个 pair,first 为宽度(像素),second 为高度(像素); + * 如果渲染失败,返回 {0, 0} + * @details 该函数会检查缓存,如果文本纹理已存在则直接返回其尺寸; + * 否则创建新的纹理并缓存,然后返回尺寸。 + * 这样可以避免重复渲染相同的文本。 + * @note 返回的尺寸是矩形的宽和高 + */ + std::pair getTextSize(const std::string& text, TextStyle style); - //渲染文本 - + /** + * @brief 将文本渲染到指定位置 + * @param text 要渲染的文本内容。支持任意 UTF-8 字符串 + * @param style 文本样式,包含字体 ID、大小、颜色等信息 + * @param x 文本左上角的 X 坐标(像素) + * @param y 文本左上角的 Y 坐标(像素) + * @details 该函数会检查缓存,如果文本纹理已存在则直接使用; + * 否则创建新的纹理、缓存并渲染。 + * 使用浮点数坐标进行渲染,支持亚像素精度。 + * @note (x, y) 为文本左上角的顶点坐标 + */ void renderText(const std::string& text, TextStyle style, int x, int y); -}; \ No newline at end of file + /** + * @brief 为文本和样式生成哈希值 + * @param text 文本内容 + * @param style 文本样式 + * @return 返回组合哈希值,用于缓存键的生成 + * @details 利用文本内容、字体 ID、字体大小和颜色信息生成唯一的哈希值, + * 用于在缓存中快速查找已渲染的文本纹理 + * @see m_cache + */ + size_t makeHash(const std::string& text, TextStyle style) const; + + /** + * @brief 手动清理所有缓存 + * @details 释放所有缓存中的纹理资源,完全清空缓存表 + * @note 清理后任何文本都需要重新渲染 + */ + void clearCache(); + +private: + /** @brief SDL 渲染器指针,用于创建纹理和执行 2D 渲染操作 */ + SDL_Renderer* m_renderer; + + /** @brief 字体管理器指针,用于管理和获取字体资源 */ + FontManager* m_fontManager; + + /** + * @struct CachedText + * @brief 缓存的文本纹理信息 + * @details 存储已渲染文本的纹理及其尺寸信息,用于快速重用 + */ + struct CachedText { + /** @brief 文本渲染后生成的 SDL 纹理指针 */ + SDL_Texture* texture; + + /** @brief 文本纹理的宽度(像素) */ + int width; + + /** @brief 文本纹理的高度(像素) */ + int height; + + /** @brief 该缓存项的最后访问时间戳(秒) */ + time_t lastAccessTime; + }; + + /** + * @brief 文本纹理缓存容器 + * @details 使用无序哈希表存储已渲染的文本纹理。 + * 键是由文本内容、字体信息等生成的哈希值(通过 makeHash 生成); + * 值是对应的 CachedText 结构体,包含纹理指针和尺寸信息。 + * + * 缓存机制的优势: + * - 避免重复渲染相同的文本 + * - 提高渲染性能 + * - 加速尺寸查询 + * @see CachedText, makeHash() + */ + std::unordered_map m_cache; + + /** @brief 最大缓存条目数,超过此数量会触发自动清理 */ + static constexpr size_t MAX_CACHE_SIZE = 256; + + /** @brief 缓存清理时保留的最少条目数(清理时会保留最常用的条目) */ + static constexpr size_t MIN_CACHE_SIZE = 128; + + /** @brief 自动清理缓存(当缓存超过最大限制时调用) */ + void autoCleanCache(); + + /** + * @brief 创建文本纹理(暂未使用) + * @param text 文本内容 + * @param fontID 字体 ID + * @param color 文本颜色 + * @return 返回创建的纹理指针,创建失败时返回 nullptr + * @deprecated 该函数已被 createAndCacheTexture() 取代 + */ + //SDL_Texture* createTextTexture(const std::string& text, const std::string& fontID, SDL_Color color); + + /** + * @brief 创建并缓存文本纹理 + * @param text 文本内容 + * @param style 文本样式 + * @return 返回 CachedText 结构体,包含创建的纹理和尺寸信息; + * 创建失败时返回空结构体(texture 为 nullptr) + * @details 该函数执行以下步骤: + * 1. 从字体管理器获取指定字体 + * 2. 使用 TTF 库将文本渲染为表面(Surface) + * 3. 将表面转换为 SDL 纹理 + * 4. 立即释放表面以节省内存 + * 5. 将纹理和尺寸信息存储到缓存中 + * 6. 返回结果 + * @see CachedText, m_cache + */ + CachedText createAndCacheTexture(const std::string& text, TextStyle style); + +}; diff --git a/src/graphics/font/Textstyle.h b/src/graphics/font/Textstyle.h index 2dffc0f..927714b 100644 --- a/src/graphics/font/Textstyle.h +++ b/src/graphics/font/Textstyle.h @@ -10,9 +10,9 @@ * 用于统一管理文本外观 */ struct TextStyle { - std::string fontID; ///< 字体标识符(在FontManager中注册的ID) - int fontSize = 16; ///< 字体大小(像素) - SDL_Color color = {255, 255, 255, 255}; ///< 文本颜色(RGBA) + std::string fontID = "SourceHanSansSC-Regular.otf"; ///< 字体标识符(在FontManager中注册的ID) + int fontSize = 24; ///< 字体大小(像素) + SDL_Color color = {0, 0, 0, 255}; ///< 文本颜色(RGBA) /** * @brief 比较两个样式是否相等(用于缓存查找) @@ -27,21 +27,4 @@ struct TextStyle { color.b == other.color.b && color.a == other.color.a; } - - /** - * @brief 计算样式的哈希值(用于unordered_map) - * @return size_t 哈希值 - */ - size_t hash() const { - // 使用组合哈希技术 - size_t h1 = std::hash{}(fontID); - size_t h2 = std::hash{}(fontSize); - size_t h3 = std::hash{}(color.r); - size_t h4 = std::hash{}(color.g); - size_t h5 = std::hash{}(color.b); - size_t h6 = std::hash{}(color.a); - - // 组合哈希(使用黄金比例乘法混合) - return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3) ^ (h5 << 4) ^ (h6 << 5); - } }; \ No newline at end of file