From 903fdc37d1e2dcbe36b61581fdff4d4f7fbf790f Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Tue, 23 Dec 2025 16:05:34 +0800 Subject: [PATCH] Let UI render on the physical screen rather than the logical screen --- src/core/GameApplication.cpp | 21 +++++- src/core/GameApplication.h | 2 +- src/core/WindowManager.cpp | 111 +++++++++++++++++++++++----- src/core/WindowManager.h | 26 +++++-- src/graphics/font/TextRenderer.cpp | 115 +++++++++++++++++------------ src/graphics/font/TextRenderer.h | 14 +++- src/graphics/ui/UIRenderer.cpp | 23 +++--- 7 files changed, 221 insertions(+), 91 deletions(-) diff --git a/src/core/GameApplication.cpp b/src/core/GameApplication.cpp index a4457aa..8a52fcd 100644 --- a/src/core/GameApplication.cpp +++ b/src/core/GameApplication.cpp @@ -30,7 +30,7 @@ bool GameApplication::initialize() { // 字体管理 m_fontManager = std::make_unique(); // 文字渲染 - m_textRenderer = std::make_unique(m_windowManager->GetRenderer(), m_fontManager.get()); + m_textRenderer = std::make_unique(m_windowManager->GetRenderer(), m_fontManager.get(), m_windowManager->getViewport()); m_uiRenderer = std:: make_unique(m_windowManager->GetRenderer(), m_textRenderer.get()); @@ -54,12 +54,27 @@ SDL_AppResult GameApplication::handleInputEvent(SDL_Event* event) { } m_sceneManager->handleMousePosition(input.mouseCurrentPosition); m_windowManager->setFullscreen(input.isFullscreen); + // 改变窗口时清理旧的缓存 + if (event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) { + m_textRenderer->clearCache(); + } + return result; + } void GameApplication::run() { - m_windowManager->Clear(); + m_sceneManager->updateCurrent(); - m_sceneManager->renderCurrent(); + + m_windowManager->Clear(); + m_windowManager->beginWorld(); + m_sceneManager->renderWorld(); + m_windowManager->endWorld(); + + m_windowManager->beginUI(); + m_sceneManager->renderUI(); + m_windowManager->endUI(); + m_windowManager->Present(); } \ No newline at end of file diff --git a/src/core/GameApplication.h b/src/core/GameApplication.h index 430216e..c5f1d0f 100644 --- a/src/core/GameApplication.h +++ b/src/core/GameApplication.h @@ -21,7 +21,7 @@ private: std::unique_ptr m_textRenderer; std::unique_ptr m_uiRenderer; - GameConfig m_config; + WindowConfig m_config; public: GameApplication(); diff --git a/src/core/WindowManager.cpp b/src/core/WindowManager.cpp index 888860b..1b6eb12 100644 --- a/src/core/WindowManager.cpp +++ b/src/core/WindowManager.cpp @@ -10,14 +10,14 @@ WindowManager::~WindowManager() { Shutdown(); } -bool WindowManager::Initialize(GameConfig& config) { - m_logicalWidth = config.logicalWidth; - m_logicalHeight = config.logicalHeight; +bool WindowManager::Initialize(WindowConfig& config) { + m_viewport.logicalWidth = config.logicalWidth; + m_viewport.logicalHeight = config.logicalHeight; // 创建窗口(支持高DPI和横屏)[3,4](@ref) m_window = SDL_CreateWindow( "孢子棋", // 窗口标题,显示在标题栏上 - m_logicalWidth, // 窗口的逻辑宽度(例如 800),用于统一布局,不受屏幕 DPI 影响 - m_logicalHeight, // 窗口的逻辑高度(例如 600) + m_viewport.logicalWidth, // 窗口的逻辑宽度(例如 800),用于统一布局,不受屏幕 DPI 影响 + m_viewport.logicalHeight, // 窗口的逻辑高度(例如 600) SDL_WINDOW_HIGH_PIXEL_DENSITY | // 启用高像素密度支持(HiDPI/Retina),确保在高分屏上画面清晰 SDL_WINDOW_RESIZABLE // 允许用户调整窗口大小(可拉伸) ); @@ -52,8 +52,8 @@ bool WindowManager::Initialize(GameConfig& config) { m_renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, - m_logicalWidth, - m_logicalHeight + m_viewport.logicalWidth, + m_viewport.logicalHeight ); // 设置纹理缩放模式为最近邻 SDL_SetTextureScaleMode(m_logicalTexture, SDL_SCALEMODE_NEAREST); @@ -67,13 +67,12 @@ void WindowManager::Shutdown() { } void WindowManager::Clear() { - // 1. 设置渲染目标为逻辑纹理 - SDL_SetRenderTarget(m_renderer, m_logicalTexture); + // 设置画笔颜色 SDL_SetRenderDrawColor(m_renderer, 255, 255, 255, 255); - // 使用画笔颜色填充整个逻辑画布 + // 使用画笔颜色填充整个画布 SDL_RenderClear(m_renderer); - + onWindowResize(); } /* @@ -89,21 +88,40 @@ void WindowManager::Clear() { void WindowManager::Present() { - // 4. 切回默认渲染目标(窗口) - SDL_SetRenderTarget(m_renderer, nullptr); - SDL_RenderClear(m_renderer); // 清的是窗口(黑边 - SDL_FRect dstRect; - calculateDstRect(dstRect); + + + //SDL_FRect dstRect; + //calculateDstRect(dstRect); + SDL_RenderTexture ( m_renderer, m_logicalTexture, // 源:你已经画好的逻辑画布 nullptr, // srcRect:源区域(nullptr = 整张) - &dstRect // dstRect:贴到哪里 & 贴多大 + &m_viewport.dst // dstRect:贴到哪里 & 贴多大 ); SDL_RenderPresent(m_renderer); } +void WindowManager::beginWorld() { + // 设置渲染目标为逻辑纹理 + SDL_SetRenderTarget(m_renderer, m_logicalTexture); +} + +void WindowManager::endWorld() { + +} + +void WindowManager::beginUI() { + // 设置渲染目标为窗口 + SDL_SetRenderTarget(m_renderer, nullptr); +} + +void WindowManager::endUI() { + // 恢复渲染目标为默认(窗口) + SDL_SetRenderTarget(m_renderer, nullptr); +} + SDL_Renderer* WindowManager::GetRenderer() { return m_renderer; } @@ -121,7 +139,7 @@ bool WindowManager::setFullscreen(bool isFullscreen) { return true; } - +/* void WindowManager::calculateDstRect(SDL_FRect& dstRect) { // 获取窗口的实际尺寸(像素) SDL_GetWindowSize(m_window, &m_windowWidth, &m_windowHeight); @@ -136,4 +154,59 @@ void WindowManager::calculateDstRect(SDL_FRect& dstRect) { // 居中显示 dstRect.x = static_cast((m_windowWidth - dstRect.w) / 2); dstRect.y = static_cast((m_windowHeight - dstRect.h) / 2); -} \ No newline at end of file +} + */ +void WindowManager::onWindowResize() { + + // ================================================= + // 1. 查询窗口的真实像素尺寸 + // 注意:这是“窗口坐标空间”的大小 + // ================================================= + SDL_GetWindowSize( + m_window, + &m_viewport.windowWidth, + &m_viewport.windowHeight + ); + + // ================================================= + // 2. 计算 X / Y 方向最多能放大多少倍 + // 使用整数除法,保证整数缩放(像素不模糊) + // ================================================= + int scaleX = m_viewport.windowWidth / m_viewport.logicalWidth; + int scaleY = m_viewport.windowHeight / m_viewport.logicalHeight; + + // ================================================= + // 3. 取较小值,保证逻辑画面完整显示 + // 同时确保最小为 1(窗口再小也能画) + // ================================================= + m_viewport.scale = std::max(1, std::min(scaleX, scaleY)); + + // ================================================= + // 4. 计算逻辑画面放大后的实际尺寸 + // ================================================= + m_viewport.dst.w = + static_cast(m_viewport.logicalWidth * + m_viewport.scale); + + m_viewport.dst.h = + static_cast(m_viewport.logicalHeight * + m_viewport.scale); + + // ================================================= + // 5. 计算居中偏移(letterbox / pillarbox) + // ================================================= + m_viewport.dst.x = + (m_viewport.windowWidth - m_viewport.dst.w) * 0.5f; + + m_viewport.dst.y = + (m_viewport.windowHeight - m_viewport.dst.h) * 0.5f; + + // 到这里为止: + // - scale 可供所有系统使用 + // - dst 是渲染 & 输入转换的唯一依据 + // - windowSize 不需要再到处 SDL_GetWindowSize +} + +Viewport const& WindowManager::getViewport() const { + return m_viewport; +} diff --git a/src/core/WindowManager.h b/src/core/WindowManager.h index 6d0c02c..5e5c166 100644 --- a/src/core/WindowManager.h +++ b/src/core/WindowManager.h @@ -6,16 +6,26 @@ public: WindowManager(); ~WindowManager(); // 初始化 - bool Initialize(GameConfig& config); + bool Initialize(WindowConfig& config); //关闭 void Shutdown(); //清理上一帧窗口 + void beginWorld(); + + void endWorld(); + + void beginUI(); + + void endUI(); + void Clear(); //呈现窗口 void Present(); bool setFullscreen(bool isFullscreen); - + + const Viewport& getViewport() const; + SDL_Renderer* GetRenderer(); SDL_Window* GetWindow(); @@ -23,13 +33,15 @@ public: private: SDL_Window* m_window; SDL_Renderer* m_renderer; - int m_logicalWidth; - int m_logicalHeight; - int m_windowWidth; - int m_windowHeight; + Viewport m_viewport; + //int m_logicalWidth; + //int m_logicalHeight; + //int m_windowWidth; + //int m_windowHeight; bool m_isFullscreen = false; SDL_Texture* m_logicalTexture = nullptr; // 计算缩放后的目标矩形 - void calculateDstRect(SDL_FRect& dstRect); + //void calculateDstRect(SDL_FRect& dstRect); + void onWindowResize(); }; \ No newline at end of file diff --git a/src/graphics/font/TextRenderer.cpp b/src/graphics/font/TextRenderer.cpp index dd9207b..cc63ebc 100644 --- a/src/graphics/font/TextRenderer.cpp +++ b/src/graphics/font/TextRenderer.cpp @@ -1,9 +1,11 @@ #include "TextRenderer.h" #include #include -TextRenderer::TextRenderer(SDL_Renderer* renderer, FontManager* fontManager) : +#include +TextRenderer::TextRenderer(SDL_Renderer* renderer, FontManager* fontManager, const Viewport& viewport) : m_fontManager(fontManager), - m_renderer(renderer) + m_renderer(renderer), + m_viewport(viewport) { //m_bitmapFont = std::make_unique(); //m_bitmapFont->load("assets/fonts/sanhan.fnt", renderer); @@ -13,7 +15,7 @@ TextRenderer::~TextRenderer() { clearCache(); } -std::pair TextRenderer::getTextSize(const std::string& text, TextStyle style) { +std::pair TextRenderer::getLogicalTextSize(const std::string& text, TextStyle style) { auto key = makeHash(text, style); auto it = m_cache.find(key); @@ -29,46 +31,60 @@ std::pair TextRenderer::getTextSize(const std::string& text, TextStyle if (!cached.texture) { return {0, 0}; } - - return {cached.width, cached.height}; + + return {cached.width / m_viewport.scale, cached.height / m_viewport.scale}; } -void TextRenderer::renderText(const std::string& text, TextStyle style, int x, int y) { - auto key = makeHash(text, style); - auto it = m_cache.find(key); +void TextRenderer::renderText(const std::string& text, TextStyle style, int logicalX, int logicalY) { - // 查找缓存 - 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) }; - - // 绘制材质 NULL 的含义:绘制整个纹理(从 (0,0) 到纹理的完整宽高) &dest 目标区域 - SDL_RenderTexture(m_renderer, it->second.texture, NULL, &dest); - return; - } - - // 创建并缓存纹理 - CachedText cached = createAndCacheTexture(text, style); + CachedText& cached = createAndCacheTexture(text, style); if (!cached.texture) { return; } + // =============================== + // 1. 逻辑坐标 → 窗口坐标 + // =============================== + SDL_FPoint winPos = { + m_viewport.dst.x + logicalX * m_viewport.scale, + m_viewport.dst.y + logicalY * m_viewport.scale + }; + + SDL_FRect dst { + winPos.x, + winPos.y, + static_cast(cached.width), + static_cast(cached.height) + }; + //std::cout << "Rendering text at (" << dst.x << ", " << dst.y << ") with size (" << dst.w << ", " << dst.h << ")\n"; + // 渲染 - SDL_FRect dest = { static_cast(x), static_cast(y), - static_cast(cached.width), - static_cast(cached.height) }; - SDL_RenderTexture(m_renderer, cached.texture, NULL, &dest); + SDL_RenderTexture( + m_renderer, + cached.texture, + nullptr, + &dst + ); //m_bitmapFont->drawText(text, x, y); } -TextRenderer::CachedText TextRenderer::createAndCacheTexture(const std::string& text, TextStyle style) { - CachedText result = {nullptr, 0, 0, std::time(nullptr)}; +TextRenderer::CachedText& TextRenderer::createAndCacheTexture(const std::string& text, TextStyle style) { + auto key = makeHash(text, style); + CachedText& slot = m_cache[key]; + // empty: 用于失败返回的只读哨兵对象(texture == nullptr) + static CachedText empty{}; + // 如果已经存在,直接返回 + if (slot.texture) { + slot.lastAccessTime = std::time(nullptr); + return slot; + } + if (slot.texture) { + SDL_DestroyTexture(slot.texture); + } + // 创建逻辑 + slot = {}; // reset + slot.lastAccessTime = std::time(nullptr); /* // 获取字体 - 需要大号字体用于高清渲染 const int TARGET_SCALE = 4; // 4倍超采样 @@ -129,17 +145,19 @@ TextRenderer::CachedText TextRenderer::createAndCacheTexture(const std::string& } */ // 获取字体 - TTF_Font* font = m_fontManager->getFont(style.fontID, style.fontSize); + TTF_Font* font = m_fontManager->getFont(style.fontID, style.fontSize * m_viewport.scale); if (!font) { SDL_Log("错误:字体未找到 %s\n", style.fontID.c_str()); - return result; + m_cache.erase(key); + return empty; } // 创建文字表面 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; + m_cache.erase(key); + return empty; } // 创建纹理 @@ -150,26 +168,24 @@ TextRenderer::CachedText TextRenderer::createAndCacheTexture(const std::string& if (!texture) { SDL_Log("错误:无法创建纹理\n"); - return result; + m_cache.erase(key); + return empty; } // 设置纹理缩放模式为最近邻 - SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); + //SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); // 保存结果 - result.texture = texture; - result.width = width; - result.height = height; - result.lastAccessTime = std::time(nullptr); - - // 保存到缓存 - auto key = makeHash(text, style); - m_cache[key] = result; + slot.texture = texture; + slot.width = width; + slot.height = height; + slot.lastAccessTime = std::time(nullptr); + // 检查是否需要清理缓存 if (m_cache.size() > MAX_CACHE_SIZE) { - autoCleanCache(); + autoCleanCache(key); } - - return result; + + return slot; } @@ -208,7 +224,7 @@ void TextRenderer::clearCache() { } -void TextRenderer::autoCleanCache() { +void TextRenderer::autoCleanCache(size_t keepKey) { // 如果缓存没有超过最大限制,则不进行清理 if (m_cache.size() <= MAX_CACHE_SIZE) { return; @@ -219,6 +235,7 @@ void TextRenderer::autoCleanCache() { cacheItems.reserve(m_cache.size()); for (const auto& pair : m_cache) { + if (pair.first == keepKey) continue; cacheItems.emplace_back(pair.first, pair.second.lastAccessTime); } @@ -236,8 +253,10 @@ void TextRenderer::autoCleanCache() { // 删除最旧的缓存项 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); } diff --git a/src/graphics/font/TextRenderer.h b/src/graphics/font/TextRenderer.h index 20312d4..9015ac0 100644 --- a/src/graphics/font/TextRenderer.h +++ b/src/graphics/font/TextRenderer.h @@ -18,6 +18,7 @@ #include "FontManager.h" #include "BitmapFont.h" #include +#include "utils/Config.h" /** * @class TextRenderer * @brief 文本渲染器 @@ -37,7 +38,7 @@ public: * @param fontManager 字体管理器指针,用于获取指定字体 ID 和大小的字体对象。不可为 nullptr * @details 初始化文本渲染器,设置必要的 SDL 渲染器和字体管理器引用 */ - TextRenderer(SDL_Renderer* renderer, FontManager* fontManager); + TextRenderer(SDL_Renderer* renderer, FontManager* fontManager, const Viewport& viewport); /** * @brief 析构函数 @@ -56,7 +57,7 @@ public: * 这样可以避免重复渲染相同的文本。 * @note 返回的尺寸是矩形的宽和高 */ - std::pair getTextSize(const std::string& text, TextStyle style); + std::pair getLogicalTextSize(const std::string& text, TextStyle style); /** * @brief 将文本渲染到指定位置 @@ -89,6 +90,8 @@ public: */ void clearCache(); + Viewport getViewport() const {return m_viewport;} + private: /** @brief SDL 渲染器指针,用于创建纹理和执行 2D 渲染操作 */ SDL_Renderer* m_renderer; @@ -96,6 +99,9 @@ private: /** @brief 字体管理器指针,用于管理和获取字体资源 */ FontManager* m_fontManager; + /** @brief 视口信息引用,用于坐标转换和渲染位置计算 */ + const Viewport& m_viewport; + /** * @struct CachedText * @brief 缓存的文本纹理信息 @@ -136,7 +142,7 @@ private: static constexpr size_t MIN_CACHE_SIZE = 128; /** @brief 自动清理缓存(当缓存超过最大限制时调用) */ - void autoCleanCache(); + void autoCleanCache(size_t keepKey); /** * @brief 创建文本纹理(暂未使用) @@ -163,6 +169,6 @@ private: * 6. 返回结果 * @see CachedText, m_cache */ - CachedText createAndCacheTexture(const std::string& text, TextStyle style); + CachedText& createAndCacheTexture(const std::string& text, TextStyle style); //std::unique_ptr m_bitmapFont; }; diff --git a/src/graphics/ui/UIRenderer.cpp b/src/graphics/ui/UIRenderer.cpp index f7ab7a0..68ffc66 100644 --- a/src/graphics/ui/UIRenderer.cpp +++ b/src/graphics/ui/UIRenderer.cpp @@ -69,12 +69,17 @@ void UIRenderer::renderButtonBackground(const ButtonData& buttonData) { auto m_rect = buttonData.rect; SDL_SetRenderDrawColor(m_renderer, m_backgroundColor.r, m_backgroundColor.g, m_backgroundColor.b, m_backgroundColor.a); - auto [width, height] = m_textRenderer->getTextSize(buttonData.text, buttonData.textstytle); + //auto [width, height] = m_textRenderer->getLogicalTextSize(buttonData.text, buttonData.textstytle); + auto viewport = m_textRenderer->getViewport(); + SDL_FPoint winPos = { + viewport.dst.x + m_rect.x * viewport.scale, + viewport.dst.y + m_rect.y * viewport.scale + }; // 绘制普通矩形 - SDL_FRect rect = { static_cast(m_rect.x) + 0.375f, - static_cast(m_rect.y) + 0.375f, - static_cast(m_rect.w) - 0.75f, - static_cast(m_rect.h) - 0.75f }; + SDL_FRect rect = { winPos.x , + winPos.y , + static_cast(m_rect.w * viewport.scale) , + static_cast(m_rect.h * viewport.scale) }; //SDL_SetRenderDrawBlendMode(m_renderer, SDL_BLENDMODE_NONE); SDL_RenderFillRect(m_renderer, &rect); } @@ -89,7 +94,7 @@ void UIRenderer::renderText(const Type& data) { auto m_rect = data.rect; auto m_textStyle = data.textstytle; if (m_text.empty()) return; - + // 这个计算公式有问题,导致放大之后文字位置会出现问题,故舍弃 // 计算文本位置(居中) // 这里需要TextRenderer的实际实现 // 假设TextRenderer有一个renderText方法: @@ -97,7 +102,7 @@ void UIRenderer::renderText(const Type& data) { // const TextStyle& style, int x, int y); // 获取文本实际尺寸以便正确居中渲染 - auto [textW, textH] = m_textRenderer->getTextSize(m_text, m_textStyle); + /*auto [textW, textH] = m_textRenderer->getLogicalTextSize(m_text, m_textStyle); // 如果组件的宽高为 0,则使用文本尺寸填充(相当于自动调整控件大小) int boxW = m_rect.w; @@ -108,7 +113,7 @@ void UIRenderer::renderText(const Type& data) { // 计算文本左上角坐标以实现居中 int textX = m_rect.x + (boxW - textW) / 2; int textY = m_rect.y + (boxH - textH) / 2; - +*/ // 渲染文本(TextRenderer 的 x,y 为左上角) - m_textRenderer->renderText(m_text, m_textStyle, textX, textY); + m_textRenderer->renderText(m_text, m_textStyle, m_rect.x, m_rect.y); }