diff --git a/docs/TEXT_RENDERING.md b/docs/TEXT_RENDERING.md new file mode 100644 index 0000000..02b4900 --- /dev/null +++ b/docs/TEXT_RENDERING.md @@ -0,0 +1,343 @@ +# MTSDF文本渲染系统使用指南 + +## 概述 + +Mirage采用MTSDF(Multi-channel Signed Distance Field)技术进行文本渲染,提供高质量的文本显示效果,支持任意缩放而不失真。 + +## 系统架构 + +文本渲染系统由以下核心组件组成: + +### 1. 字体管理器 (`font_manager`) +负责加载和管理TTF/OTF字体文件,与FreeType集成。 + +```cpp +#include "text/font_manager.h" + +// 创建字体管理器 +mirage::render::text::font_manager font_mgr; + +// 加载字体 +auto font_result = font_mgr.load_font("path/to/font.ttf"); +if (font_result.has_value()) { + auto font_id = font_result.value(); + // 使用font_id... +} +``` + +### 2. 字形缓存 (`glyph_cache`) +按需生成MTSDF纹理并缓存到图集中。 + +```cpp +#include "text/glyph_cache.h" + +// 创建字形缓存(需要font_manager引用) +mirage::render::text::glyph_cache cache(font_mgr); +``` + +### 3. 文本排版器 (`text_shaper`) +将文本字符串转换为可渲染的顶点数据。 + +```cpp +#include "text/text_shaper.h" + +// 创建文本排版器 +mirage::render::text::text_shaper shaper(font_mgr, cache); + +// 排版文本 +auto shaped = shaper.shape_text( + U"Hello, World!", // UTF-32文本 + font_id, // 字体ID + 24.0f, // 字体大小(像素) + mirage::vec2f_t(100, 100), // 位置 + mirage::color::white() // 颜色 +); + +// UTF-8转UTF-32 +auto utf32_text = text_shaper::utf8_to_utf32("你好,世界!"); +``` + +### 4. 文本渲染器 (`text_renderer`) +将排版后的顶点数据渲染到屏幕。 + +```cpp +#include "pipeline/text_renderer.h" + +// 创建文本渲染器 +mirage::render::text_renderer renderer(device, res_mgr, cache); + +// 初始化(在render pass创建后) +renderer.initialize(render_pass); + +// 帧开始 +renderer.begin_frame(frame_index, viewport_size); + +// 渲染文本批次 +mirage::render::text_batch batch; +batch.font_id = font_id; +batch.vertices = shaped.vertices; +batch.indices = shaped.indices; +batch.shader_id = mirage::render::text_renderer::STANDARD_SHADER_ID; + +renderer.render(cmd, batch); + +// 帧结束 +renderer.end_frame(frame_index); +``` + +## 基础用法 + +### 使用render_collector绘制文本 + +在 Widget 系统中,文本渲染通过 `render_collector` 进行收集,然后由渲染管线处理。Widget 可以使用 `text_command` 或 `text_effect_command` 来发出文本绘制指令: + +```cpp +#include "common/render_command.h" + +// 在 Widget 的 collect_render_commands 方法中: +void my_widget::collect_render_commands(render_collector& collector) { + // 1. 基础文本绘制 + collector.add_command(text_command( + mirage::vec2f_t(100, 100), // 位置 + "Hello, World!", // 文本(UTF-8) + mirage::color::white(), // 颜色 + 24.0f // 字体大小 + )); + + // 2. 使用带特效的文本 + text_effect_command cmd; + cmd.position = mirage::vec2f_t(100, 150); + cmd.text = "Custom Font"; + cmd.text_color = mirage::color::white(); + cmd.font_size = 24.0f; + cmd.font_id = custom_font_id; + collector.add_command(std::move(cmd)); +} +``` + +## 文本特效 + +### 描边效果 + +```cpp +// 使用便捷方法 +builder.draw_text_outlined( + mirage::vec2f_t(100, 200), + "Outlined Text", + mirage::color::white(), // 文本颜色 + 24.0f, // 字体大小 + mirage::color::black(), // 描边颜色 + 0.1f // 描边宽度 +); + +// 或使用完整API +mirage::text_effect_params params; +params.outline_color = mirage::color::black(); +params.outline_width = 0.15f; +params.outline_softness = 0.1f; + +builder.draw_text_with_effect( + mirage::vec2f_t(100, 200), + "Outlined Text", + mirage::color::white(), + 24.0f, + mirage::text_effect_type::outline, + params +); +``` + +### 阴影效果 + +```cpp +// 使用便捷方法 +builder.draw_text_shadowed( + mirage::vec2f_t(100, 250), + "Text with Shadow", + mirage::color::white(), // 文本颜色 + 24.0f, // 字体大小 + mirage::color::from_rgba(0, 0, 0, 128), // 阴影颜色 + mirage::vec2f_t(2.0f, 2.0f) // 阴影偏移 +); + +// 或使用完整API +mirage::text_effect_params params; +params.shadow_color = mirage::color::from_rgba(0, 0, 0, 128); +params.shadow_offset = mirage::vec2f_t(3.0f, 3.0f); +params.shadow_softness = 0.15f; + +builder.draw_text_with_effect( + mirage::vec2f_t(100, 250), + "Text with Shadow", + mirage::color::white(), + 24.0f, + mirage::text_effect_type::shadow, + params +); +``` + +## 自定义着色器 + +### 注册自定义着色器 + +```cpp +// 1. 编写GLSL片段着色器(见shaders/widget/mtsdf_text_*.frag.glsl) + +// 2. 编译为SPIR-V + +// 3. 注册到渲染器 +std::vector custom_spirv = load_spirv("custom_text.frag.spv"); +uint32_t custom_shader_id = 100; // 自定义ID(>= 100) + +bool success = renderer.register_custom_shader(custom_shader_id, custom_spirv); +``` + +### 使用自定义着色器 + +```cpp +mirage::text_effect_params params; +params.custom_shader_id = custom_shader_id; + +builder.draw_text_with_effect( + position, + text, + color, + size, + mirage::text_effect_type::custom, + params +); +``` + +## 内置着色器ID + +系统提供以下内置着色器: + +- `STANDARD_SHADER_ID = 0` - 标准MTSDF渲染 +- `OUTLINE_SHADER_ID = 1` - 描边效果 +- `SHADOW_SHADER_ID = 2` - 阴影效果 + +自定义着色器ID应从100开始。 + +## 性能优化建议 + +### 1. 字形预加载 +对于常用字符,可以预先生成MTSDF缓存: + +```cpp +// 预加载ASCII字符 +for (char32_t c = 32; c < 127; ++c) { + cache.get_or_create_glyph(font_id, c); +} + +// 预加载中文常用字 +std::u32string common_chars = U"的一是在了不和有大这"; +for (char32_t c : common_chars) { + cache.get_or_create_glyph(font_id, c); +} +``` + +### 2. 批次合并 +相同字体的文本应合并到一个批次中渲染: + +```cpp +mirage::render::text_batch batch; +batch.font_id = font_id; + +// 合并多个文本的顶点数据 +auto text1 = shaper.shape_text(...); +auto text2 = shaper.shape_text(...); + +batch.vertices.insert(batch.vertices.end(), text1.vertices.begin(), text1.vertices.end()); +batch.indices.insert(batch.indices.end(), text1.indices.begin(), text1.indices.end()); + +// 调整text2的索引偏移 +uint32_t offset = static_cast(text1.vertices.size()); +for (auto idx : text2.indices) { + batch.indices.push_back(idx + offset); +} +batch.vertices.insert(batch.vertices.end(), text2.vertices.begin(), text2.vertices.end()); + +// 一次性渲染 +renderer.render(cmd, batch); +``` + +### 3. 图集纹理管理 +- 默认图集大小为2048x2048,可根据需求调整 +- 当图集空间不足时,考虑增加图集大小或使用多个图集 +- 使用`atlas.is_dirty()`检查是否需要更新GPU纹理 + +```cpp +if (cache.get_atlas().is_dirty()) { + renderer.update_atlas_texture(); +} +``` + +## 注意事项 + +1. **字体加载**:确保字体文件路径正确,支持TTF和OTF格式 +2. **UTF-8编码**:所有文本输入应使用UTF-8编码 +3. **线程安全**:`font_manager`和`glyph_cache`不是线程安全的,应在渲染线程中使用 +4. **资源管理**:`text_renderer`会自动管理GPU资源,调用`cleanup()`时释放 +5. **性能监控**:大量动态文本会触发频繁的MTSDF生成,建议缓存静态文本 + +## 常见问题 + +### Q: 文本显示模糊? +A: 检查字体大小和缩放比例,MTSDF的基准大小为48像素,过小的字体可能需要调整`glyph_cache`的`glyph_size`参数。 + +### Q: 某些字符不显示? +A: 确认字体文件包含该字符的字形,使用`font_mgr.get_glyph_index()`检查字形是否存在。 + +### Q: 图集空间不足? +A: 创建`glyph_cache`时增加`atlas_size`参数(默认2048),或实现多图集支持。 + +### Q: 渲染性能差? +A: +- 减少每帧的文本更新 +- 合并相同字体的渲染批次 +- 预加载常用字符 +- 使用静态文本缓存 + +## 示例:完整的文本渲染流程 + +```cpp +// 1. 初始化 +mirage::render::text::font_manager font_mgr; +auto font_result = font_mgr.load_font("fonts/NotoSans-Regular.ttf"); +auto font_id = font_result.value(); + +mirage::render::text::glyph_cache cache(font_mgr); +mirage::render::text::text_shaper shaper(font_mgr, cache); +mirage::render::text_renderer renderer(device, res_mgr, cache); +renderer.initialize(render_pass); + +// 2. 每帧渲染 +renderer.begin_frame(frame_index, viewport_size); + +// 使用text_shaper生成顶点 +auto shaped = shaper.shape_text( + text_shaper::utf8_to_utf32("Hello, MTSDF!"), + font_id, + 32.0f, + mirage::vec2f_t(100, 100), + mirage::color::white() +); + +// 创建批次并渲染 +mirage::render::text_batch batch; +batch.font_id = font_id; +batch.vertices = std::move(shaped.vertices); +batch.indices = std::move(shaped.indices); +batch.shader_id = mirage::render::text_renderer::STANDARD_SHADER_ID; + +renderer.render(cmd, batch); +renderer.end_frame(frame_index); + +// 3. 清理 +renderer.cleanup(); +``` + +## 参考资料 + +- [MTSDF技术原理](https://github.com/Chlumsky/msdfgen) +- [FreeType文档](https://www.freetype.org/freetype2/docs/documentation.html) +- Vulkan文本渲染最佳实践 \ No newline at end of file diff --git a/example/test_thread/main.cpp b/example/test_thread/main.cpp index e183cbc..97677c9 100644 --- a/example/test_thread/main.cpp +++ b/example/test_thread/main.cpp @@ -3,9 +3,11 @@ #include "containers/overlay.h" #include "containers/scroll_box/scroll_box.h" #include "image/texture_manager.h" +#include "text/font_manager.h" #include "widgets/imager.h" #include "widgets/mask.h" #include "widgets/post_process.h" +#include "widgets/text_widget.h" using namespace mirage; @@ -28,9 +30,17 @@ int main(int argc, char* argv[]) { auto texture_id = app.texture_mgr()->load_texture("D:\\screenshot-20251128-165627.png").value(); auto tex_size = app.texture_mgr()->get_texture(texture_id)->size().cast(); + // 加载字体 + uint32_t font_id; + auto font_result = app.font_mgr()->load_font(R"(C:\Windows\Fonts\SIMYOU.TTF)"); + if (font_result.has_value()) { + font_id = font_result.value(); + } + auto root_widget = new_widget()[ + new_widget()->font_id(font_id).text("你好,我们在此相遇").font_size(24), new_widget()[ - new_widget()->texture(texture_id).source_size(tex_size).scale(scale_mode::contain), + new_widget()->texture_id(texture_id).source_size(tex_size).scale(scale_mode::contain), // 圆形遮罩 new_widget()[ // 模糊效果 (blur) @@ -38,23 +48,23 @@ int main(int argc, char* argv[]) { ]->circle() | align(alignment::center_left) ], new_widget()[ - new_widget()->texture(texture_id).source_size(tex_size).scale(scale_mode::contain), + new_widget()->texture_id(texture_id).source_size(tex_size).scale(scale_mode::contain), new_widget()->vignette() ], new_widget()[ - new_widget()->texture(texture_id).source_size(tex_size).scale(scale_mode::contain), + new_widget()->texture_id(texture_id).source_size(tex_size).scale(scale_mode::contain), new_widget()->chromatic_aberration(10, 1) ], new_widget()[ - new_widget()->texture(texture_id).source_size(tex_size).scale(scale_mode::contain), + new_widget()->texture_id(texture_id).source_size(tex_size).scale(scale_mode::contain), new_widget()->noise(20.0f, 1.f, true) ], new_widget()[ - new_widget()->texture(texture_id).source_size(tex_size).scale(scale_mode::contain), + new_widget()->texture_id(texture_id).source_size(tex_size).scale(scale_mode::contain), new_widget()->color_adjust(0.1f, 1.5f, 1.5f, 2.2f) ], new_widget()[ - new_widget()->texture(texture_id).source_size(tex_size).scale(scale_mode::contain), + new_widget()->texture_id(texture_id).source_size(tex_size).scale(scale_mode::contain), new_widget()->color_tint(color::from_rgba(255, 100, 100, 255), 0.3f, blend_mode::overlay) ] ]; diff --git a/src/app/application.cpp b/src/app/application.cpp index d176e94..72b43a9 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -11,11 +11,15 @@ #include "vulkan/swapchain.h" #include "vulkan/resource_manager.h" #include "image/texture_manager.h" +#include "text/font_manager.h" +#include "text/glyph_cache.h" +#include "text/text_shaper.h" #include "pipeline/render_pipeline.h" #include "widget_base.h" #include #include +#include "text/font_manager.h" namespace mirage::app { // ============================================================================ @@ -203,42 +207,70 @@ namespace mirage::app { // ======================================================================== texture_manager_ = std::make_unique(*resource_manager_, *logical_device_); - + // ======================================================================== - // 8. 创建渲染管线 + // 8. 创建文本渲染系统 // ======================================================================== - + + // 创建字体管理器 + font_manager_ = std::make_unique(); + + // 创建字形缓存(2048x2048 图集,48像素字形) + glyph_cache_ = std::make_unique( + *font_manager_, + 2048, // 图集大小 + 48 // 字形大小 + ); + + // 创建文本排版器 + text_shaper_ = std::make_unique( + *font_manager_, + *glyph_cache_ + ); + + // ======================================================================== + // 9. 创建渲染管线(传递文本渲染依赖) + // ======================================================================== + render_pipeline::config pipeline_config{ .frames_in_flight = 2 // 双缓冲 }; - + render_pipeline_ = std::make_unique( *logical_device_, *swapchain_, *resource_manager_, *texture_manager_, + *text_shaper_, + *glyph_cache_, pipeline_config ); - + render_pipeline_->initialize(); // ======================================================================== - // 9. 创建控件状态存储 + // 10. 创建控件状态存储 // ======================================================================== state_store_ = std::make_unique(); // ======================================================================== - // 10. 创建控件上下文 + // 11. 创建控件上下文(传递文本排版器引用) // ======================================================================== - widget_context_ = std::make_unique(*state_store_); + widget_context_ = std::make_unique(*state_store_, *text_shaper_); // ======================================================================== - // 11. 创建线程协调器 + // 12. 创建线程协调器(传递文本渲染依赖) // ======================================================================== - coordinator_ = std::make_unique(*render_pipeline_, *state_store_, *widget_context_); + coordinator_ = std::make_unique( + *render_pipeline_, + *state_store_, + *widget_context_, + *text_shaper_, + *glyph_cache_ + ); // 启动线程协调器 threading::coordinator_config coord_config{}; diff --git a/src/app/application.h b/src/app/application.h index c475378..0d6bf00 100644 --- a/src/app/application.h +++ b/src/app/application.h @@ -19,6 +19,10 @@ #include "widget_event/widget_event_router.h" namespace mirage { + namespace render::text { + class font_manager; + } + class widget_event_router; } @@ -211,6 +215,11 @@ public: /// /// @return 纹理管理器指针 [[nodiscard]] auto texture_mgr() const { return texture_manager_.get(); } + + /// @brief 获取字体管理器 + /// + /// @return 字体管理器指针 + [[nodiscard]] auto font_mgr() const { return font_manager_.get(); } // ======================================================================== // 配置查询 @@ -324,6 +333,15 @@ private: /// @brief 纹理管理器 std::unique_ptr texture_manager_; + /// @brief 字体管理器 + std::unique_ptr font_manager_; + + /// @brief 字形缓存 + std::unique_ptr glyph_cache_; + + /// @brief 文本排版器 + std::unique_ptr text_shaper_; + /// @brief 渲染管线 std::unique_ptr render_pipeline_; diff --git a/src/app/threading/layout_thread.cpp b/src/app/threading/layout_thread.cpp index 70c5044..d23c83c 100644 --- a/src/app/threading/layout_thread.cpp +++ b/src/app/threading/layout_thread.cpp @@ -19,11 +19,15 @@ namespace mirage::app::threading { render_tree_buffer& tree_buffer, frame_sync& sync, state::widget_state_store& state_store, - widget_context& context + widget_context& context, + render::text::text_shaper& text_shaper, + render::text::glyph_cache& glyph_cache ) : tree_buffer_(tree_buffer) , sync_(sync) , state_store_(state_store) - , context_(context) { + , context_(context) + , text_shaper_(text_shaper) + , glyph_cache_(glyph_cache) { } layout_thread::~layout_thread() { @@ -639,7 +643,7 @@ namespace mirage::app::threading { } // 使用 render_tree_builder 从渲染命令构建渲染树 - render_tree_builder builder; + render::render_tree_builder builder(text_shaper_, glyph_cache_); *tree = builder.build(render_commands); // 设置脏区域到写入缓冲区 diff --git a/src/app/threading/layout_thread.h b/src/app/threading/layout_thread.h index c214120..37d96ba 100644 --- a/src/app/threading/layout_thread.h +++ b/src/app/threading/layout_thread.h @@ -28,6 +28,11 @@ namespace mirage { class widget_base; } +namespace mirage::render::text { + class text_shaper; + class glyph_cache; +} + namespace mirage::app::threading { class layout_snapshot; @@ -152,11 +157,15 @@ public: /// @param sync 帧同步对象的引用 /// @param state_store 控件状态存储的引用 /// @param context 控件上下文的引用 + /// @param text_shaper 文本排版器的引用 + /// @param glyph_cache 字形缓存的引用 layout_thread( render_tree_buffer& tree_buffer, frame_sync& sync, state::widget_state_store& state_store, - widget_context& context + widget_context& context, + render::text::text_shaper& text_shaper, + render::text::glyph_cache& glyph_cache ); /// @brief 析构函数 @@ -335,6 +344,12 @@ private: /// @brief 控件上下文引用 widget_context& context_; + /// @brief 文本排版器引用 + render::text::text_shaper& text_shaper_; + + /// @brief 字形缓存引用 + render::text::glyph_cache& glyph_cache_; + /// @brief 请求队列(单生产者单消费者) mirage::threading::spsc_queue request_queue_; diff --git a/src/app/threading/thread_coordinator.cpp b/src/app/threading/thread_coordinator.cpp index dd94538..2a82e02 100644 --- a/src/app/threading/thread_coordinator.cpp +++ b/src/app/threading/thread_coordinator.cpp @@ -13,10 +13,18 @@ namespace mirage::app::threading { // 构造函数和析构函数 // ============================================================================ -thread_coordinator::thread_coordinator(render_pipeline& pipeline, state::widget_state_store& state_store, widget_context& context) +thread_coordinator::thread_coordinator( + render_pipeline& pipeline, + state::widget_state_store& state_store, + widget_context& context, + render::text::text_shaper& text_shaper, + render::text::glyph_cache& glyph_cache +) : pipeline_(pipeline) , state_store_(state_store) , context_(context) + , text_shaper_(text_shaper) + , glyph_cache_(glyph_cache) , start_time_(std::chrono::steady_clock::now()) { } @@ -289,8 +297,15 @@ void thread_coordinator::create_threads(const coordinator_config& config) { // 2. 创建渲染树双缓冲 tree_buffer_ = std::make_unique(); - // 3. 创建布局线程(传入状态存储引用和控件上下文引用) - layout_thread_ = std::make_unique(*tree_buffer_, *frame_sync_, state_store_, context_); + // 3. 创建布局线程(传入状态存储引用、控件上下文引用和文本渲染依赖) + layout_thread_ = std::make_unique( + *tree_buffer_, + *frame_sync_, + state_store_, + context_, + text_shaper_, + glyph_cache_ + ); // 4. 创建渲染线程 render_thread_ = std::make_unique(*tree_buffer_, *frame_sync_, pipeline_); diff --git a/src/app/threading/thread_coordinator.h b/src/app/threading/thread_coordinator.h index fc02bc7..6a8a184 100644 --- a/src/app/threading/thread_coordinator.h +++ b/src/app/threading/thread_coordinator.h @@ -24,6 +24,11 @@ namespace mirage { class render_pipeline; } +namespace mirage::render::text { + class text_shaper; + class glyph_cache; +} + namespace mirage::app::threading { // ============================================================================ @@ -102,7 +107,15 @@ public: /// @param pipeline 渲染管线的引用 /// @param state_store 控件状态存储的引用 /// @param context 控件上下文的引用 - explicit thread_coordinator(render_pipeline& pipeline, state::widget_state_store& state_store, widget_context& context); + /// @param text_shaper 文本排版器的引用 + /// @param glyph_cache 字形缓存的引用 + explicit thread_coordinator( + render_pipeline& pipeline, + state::widget_state_store& state_store, + widget_context& context, + render::text::text_shaper& text_shaper, + render::text::glyph_cache& glyph_cache + ); /// @brief 析构函数 /// @@ -279,6 +292,12 @@ private: /// @brief 控件上下文引用 widget_context& context_; + /// @brief 文本排版器引用 + render::text::text_shaper& text_shaper_; + + /// @brief 字形缓存引用 + render::text::glyph_cache& glyph_cache_; + /// @brief 帧同步对象 std::unique_ptr frame_sync_; diff --git a/src/common/aabb_utils.cpp b/src/common/aabb_utils.cpp new file mode 100644 index 0000000..058993f --- /dev/null +++ b/src/common/aabb_utils.cpp @@ -0,0 +1 @@ +#include "aabb_utils.h" diff --git a/src/common/render_command.cpp b/src/common/render_command.cpp deleted file mode 100644 index 38d551a..0000000 --- a/src/common/render_command.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "render_command.h" diff --git a/src/common/render_command.h b/src/common/render_command.h index adbf239..3fdee70 100644 --- a/src/common/render_command.h +++ b/src/common/render_command.h @@ -84,14 +84,55 @@ namespace mirage { std::string text; // 文本内容 color text_color; // 文本颜色 float font_size; // 字体大小 + uint32_t font_id; // 字体ID render_order order; // 渲染顺序 text_command(const vec2f_t& pos, std::string txt, - const color& col, float size) : position(pos), text(std::move(txt)), text_color(col), - font_size(size), order() { + const color& col, float size, uint32_t fid = 0) : position(pos), text(std::move(txt)), text_color(col), + font_size(size), font_id(fid), order() { } }; + // ============================================================================ + // 文本特效相关命令 + // ============================================================================ + + /// @brief 文本特效类型 + enum class text_effect_type : uint32_t { + none = 0, ///< 标准渲染 + outline = 1, ///< 描边效果 + shadow = 2, ///< 阴影效果 + custom = 100 ///< 自定义着色器 + }; + + /// @brief 文本特效参数 + struct text_effect_params { + // 描边参数 + color outline_color{0, 0, 0, 255}; + float outline_width{0.1f}; + float outline_softness{0.1f}; + + // 阴影参数 + color shadow_color{0, 0, 0, 128}; + vec2f_t shadow_offset{2.0f, 2.0f}; + float shadow_softness{0.1f}; + + // 自定义着色器ID(当effect_type == custom时使用) + uint32_t custom_shader_id{0}; + }; + + /// @brief 带特效的文本命令 + struct text_effect_command { + vec2f_t position; ///< 文本左上角位置 + std::string text; ///< 文本内容(UTF-8) + color text_color; ///< 文本颜色 + float font_size; ///< 字体大小(像素) + uint32_t font_id{0}; ///< 字体ID(0为默认字体) + text_effect_type effect_type{text_effect_type::none}; + text_effect_params effect_params; + render_order order; ///< 渲染顺序 + }; + // 图片绘制命令 struct image_command { vec2f_t position; // 图片左上角位置 @@ -540,6 +581,7 @@ namespace mirage { using render_command = std::variant< rectangle_command, text_command, + text_effect_command, image_command, debug_rect_command, post_effect_command, @@ -547,310 +589,4 @@ namespace mirage { mask_end_command >; - // 渲染命令构建器 - class render_command_builder { - public: - /// @brief 设置当前 z_order,影响后续所有命令 - /// @param z z_order 值 - /// @return 返回自身引用,支持链式调用 - auto& set_z_order(int32_t z) { - current_z_order_ = z; - return *this; - } - - /// @brief 增加 z_order - /// @param delta 增加的值(默认为 1) - /// @return 返回自身引用,支持链式调用 - auto& push_z_order(int32_t delta = 1) { - current_z_order_ += delta; - return *this; - } - - /// @brief 获取当前 z_order - /// @return 当前的 z_order 值 - [[nodiscard]] int32_t get_z_order() const { - return current_z_order_; - } - - /// @brief 推入裁剪区域(取交集) - /// @param rect 新的裁剪区域 - void push_clip(const aabb2d_t& rect) { - if (clip_stack_.empty()) { - clip_stack_.push_back(rect); - } - else { - clip_stack_.push_back(clip_stack_.back().intersection(rect)); - } - } - - /// @brief 弹出裁剪区域 - void pop_clip() { - if (!clip_stack_.empty()) { - clip_stack_.pop_back(); - } - } - - /// @brief 获取当前裁剪区域 - [[nodiscard]] std::optional get_current_clip() const { - if (clip_stack_.empty()) { - return std::nullopt; - } - return clip_stack_.back(); - } - - // 添加矩形绘制命令 - void draw_rectangle(const vec2f_t& position, const vec2f_t& size, - const color& fill_color, rect_corner_radius corner_radius = {}) { - auto cmd = rectangle_command(position, size, fill_color, corner_radius); - cmd.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(std::move(cmd)); - } - - // 添加文本绘制命令 - void draw_text(const vec2f_t& position, std::string text, - const color& text_color, float font_size) { - auto cmd = text_command(position, std::move(text), text_color, font_size); - cmd.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(std::move(cmd)); - } - - // 添加图片绘制命令 - void draw_image(const vec2f_t& position, const vec2f_t& size, - uint32_t texture_id) { - auto cmd = image_command(position, size, texture_id); - cmd.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(std::move(cmd)); - } - - // 添加调试框绘制命令(只有边框,没有填充) - void draw_debug_rect(const vec2f_t& position, const vec2f_t& size, - const color& border_color, float border_width = 1.0f, - float corner_radius = 0.0f) { - auto cmd = debug_rect_command(position, size, border_color, border_width, corner_radius); - cmd.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(std::move(cmd)); - } - - // ======================================================================== - // 后效绘制方法 - 类型安全版本 - // ======================================================================== - - /// @brief 绘制模糊效果 - /// @param position 效果区域位置 - /// @param size 效果区域大小 - /// @param radius 模糊半径 - /// @param intensity 效果强度 - void draw_blur( - const vec2f_t& position, - const vec2f_t& size, - float radius = 10.0f, - float intensity = 1.0f - ) { - auto effect = blur_effect(position, size, radius, intensity); - effect.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(post_effect_command{std::move(effect)}); - } - - /// @brief 绘制暗角效果 - /// @param position 效果区域位置 - /// @param size 效果区域大小 - /// @param radius 暗角半径 - /// @param softness 边缘柔和度 - /// @param intensity 效果强度 - void draw_vignette( - const vec2f_t& position, - const vec2f_t& size, - float radius = 0.5f, - float softness = 0.5f, - float intensity = 1.0f - ) { - auto effect = vignette_effect(position, size, radius, softness, intensity); - effect.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(post_effect_command{std::move(effect)}); - } - - /// @brief 绘制色差效果 - /// @param position 效果区域位置 - /// @param size 效果区域大小 - /// @param offset 偏移量 - /// @param intensity 效果强度 - void draw_chromatic_aberration( - const vec2f_t& position, - const vec2f_t& size, - float offset = 2.0f, - float intensity = 1.0f - ) { - auto effect = chromatic_aberration_effect(position, size, offset, intensity); - effect.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(post_effect_command{std::move(effect)}); - } - - /// @brief 绘制颜色调整效果 - /// @param position 效果区域位置 - /// @param size 效果区域大小 - /// @param brightness 亮度 - /// @param contrast 对比度 - /// @param saturation 饱和度 - /// @param gamma 伽马 - void draw_color_adjust( - const vec2f_t& position, - const vec2f_t& size, - float brightness = 0.0f, - float contrast = 1.0f, - float saturation = 1.0f, - float gamma = 1.0f - ) { - auto effect = color_adjust_effect(position, size, brightness, contrast, saturation, gamma); - effect.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(post_effect_command{std::move(effect)}); - } - - /// @brief 绘制色调效果 - /// @param position 效果区域位置 - /// @param size 效果区域大小 - /// @param tint 色调颜色 - /// @param intensity 效果强度 - /// @param blend 混合模式 - void draw_color_tint( - const vec2f_t& position, - const vec2f_t& size, - const color& tint, - float intensity = 0.5f, - blend_mode blend = blend_mode::multiply - ) { - auto effect = color_tint_effect(position, size, tint, intensity, blend); - effect.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(post_effect_command{std::move(effect)}); - } - - /// @brief 绘制噪点效果 - /// @param position 效果区域位置 - /// @param size 效果区域大小 - /// @param amount 噪点强度 - /// @param grain_size 颗粒大小 - /// @param animated 是否动画 - void draw_noise( - const vec2f_t& position, - const vec2f_t& size, - float amount = 0.1f, - float grain_size = 1.0f, - bool animated = false - ) { - auto effect = noise_effect(position, size, amount, grain_size, animated); - effect.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(post_effect_command{std::move(effect)}); - } - - /// @brief 绘制自定义着色器效果 - /// @param position 效果区域位置 - /// @param size 效果区域大小 - /// @param shader_id 着色器 ID - /// @param uniforms uniform 参数 - /// @param intensity 效果强度 - void draw_custom_effect( - const vec2f_t& position, - const vec2f_t& size, - uint32_t shader_id, - shader_uniform_map uniforms = {}, - float intensity = 1.0f - ) { - auto effect = custom_shader_effect(position, size, shader_id, std::move(uniforms), intensity); - effect.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(post_effect_command{std::move(effect)}); - } - - // ======================================================================== - // 通用后效方法(接受任意后效类型) - // ======================================================================== - - /// @brief 添加任意后效命令 - /// @tparam Effect 后效类型 - /// @param effect 后效实例 - template - void draw_effect(Effect effect) { - // 设置 z_order - effect.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(post_effect_command{std::move(effect)}); - } - - // ======================================================================== - // 遮罩方法 - // ======================================================================== - - /// @brief 开始遮罩区域 - /// @param params 遮罩参数 - /// @param content_bounds 内容边界框 - void begin_mask(const mask_params& params, const aabb2d_t& content_bounds) { - auto cmd = mask_begin_command(params, content_bounds); - cmd.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(std::move(cmd)); - } - - /// @brief 结束遮罩区域 - void end_mask() { - auto cmd = mask_end_command(); - cmd.order = render_order::at(current_z_order_, get_current_clip()); - commands_.emplace_back(std::move(cmd)); - } - - // 获取所有命令 - [[nodiscard]] const std::vector& get_commands() const { - return commands_; - } - - // ======================================================================== - // 排序方法 - // ======================================================================== - - /// @brief 获取按 z_order 排序后的命令 - /// @return 排序后的命令副本 - [[nodiscard]] auto get_sorted_commands() const -> std::vector { - auto sorted = commands_; - std::ranges::stable_sort(sorted, - [](const auto& a, const auto& b) { - return get_command_z_order(a) < get_command_z_order(b); - }); - return sorted; - } - - // 清空命令(保留容量) - void reset() { - commands_.clear(); - clip_stack_.clear(); - current_z_order_ = 0; - } - - // 预分配空间 - void reserve(size_t capacity) { - commands_.reserve(capacity); - } - - // 获取当前命令数量 - [[nodiscard]] size_t size() const { - return commands_.size(); - } - - // 检查是否为空 - [[nodiscard]] bool empty() const { - return commands_.empty(); - } - - private: - std::vector commands_; - std::vector clip_stack_; - std::vector layer_id_stack_; ///< 层 ID 栈(用于校验配对) - int32_t current_z_order_ = 0; ///< 当前 z_order,影响后续所有命令 - - /// @brief 获取命令的 z_order - static int32_t get_command_z_order(const render_command& cmd) { - return std::visit([](const auto& c) -> int32_t { - if constexpr (std::is_same_v, post_effect_command>) { - return std::visit([](const auto& effect) { return effect.order.z_order; }, c); - } - else { - return c.order.z_order; - } - }, cmd); - } - }; } // namespace mirage diff --git a/src/common/types.h b/src/common/types.h index b4c5194..2658e46 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -1,4 +1,6 @@ #pragma once +#include +#include #include #include @@ -41,4 +43,9 @@ namespace mirage { /// @brief 无效纹理ID常量 constexpr texture_id_t INVALID_TEXTURE_ID = 0; + /// @brief 结果类型,用于错误处理 + /// @tparam T 成功时的值类型 + template + using expected_t = std::expected; + } // namespace mirage diff --git a/src/render/CMakeLists.txt b/src/render/CMakeLists.txt index b205584..80e7f05 100644 --- a/src/render/CMakeLists.txt +++ b/src/render/CMakeLists.txt @@ -2,12 +2,16 @@ project(render_core) find_package(Vulkan REQUIRED) find_package(Stb REQUIRED) +find_package(Freetype REQUIRED) +find_package(msdfgen REQUIRED) simple_library(STATIC) -target_link_libraries(${PROJECT_NAME} PUBLIC - Vulkan::Vulkan +target_link_libraries(${PROJECT_NAME} PUBLIC + Vulkan::Vulkan common + Freetype::Freetype + msdfgen::msdfgen ) target_compile_definitions(${PROJECT_NAME} PUBLIC diff --git a/src/render/image/texture_manager.cpp b/src/render/image/texture_manager.cpp index 44cd332..8e93a63 100644 --- a/src/render/image/texture_manager.cpp +++ b/src/render/image/texture_manager.cpp @@ -2,357 +2,318 @@ #include "vulkan/command_buffer.h" #include -namespace mirage -{ - texture_manager::texture_manager(resource_manager &res_mgr, const logical_device &device) - : res_mgr_(res_mgr), device_(device) - { - // 创建默认采样器 - auto sampler_result = create_sampler(); - if (sampler_result) - { - default_sampler_ = sampler_result.value(); - } - } +namespace mirage { + texture_manager::texture_manager(resource_manager& res_mgr, const logical_device& device) : res_mgr_(res_mgr), + device_(device) { + // 创建默认采样器 + auto sampler_result = create_sampler(); + if (sampler_result) { + default_sampler_ = sampler_result.value(); + } + } - texture_manager::~texture_manager() - { - release_all(); + texture_manager::~texture_manager() { + release_all(); - // 销毁默认采样器 - if (default_sampler_) - { - device_.get_handle().destroySampler(default_sampler_); - default_sampler_ = VK_NULL_HANDLE; - } - } + // 销毁默认采样器 + if (default_sampler_) { + device_.get_handle().destroySampler(default_sampler_); + default_sampler_ = VK_NULL_HANDLE; + } + } - auto texture_manager::create_sampler() -> expected_t - { - vk::SamplerCreateInfo sampler_info{}; - sampler_info.magFilter = vk::Filter::eLinear; - sampler_info.minFilter = vk::Filter::eLinear; - sampler_info.addressModeU = vk::SamplerAddressMode::eRepeat; - sampler_info.addressModeV = vk::SamplerAddressMode::eRepeat; - sampler_info.addressModeW = vk::SamplerAddressMode::eRepeat; - sampler_info.anisotropyEnable = VK_FALSE; - sampler_info.maxAnisotropy = 1.0f; - sampler_info.borderColor = vk::BorderColor::eIntOpaqueBlack; - sampler_info.unnormalizedCoordinates = VK_FALSE; - sampler_info.compareEnable = VK_FALSE; - sampler_info.compareOp = vk::CompareOp::eAlways; - sampler_info.mipmapMode = vk::SamplerMipmapMode::eLinear; - sampler_info.mipLodBias = 0.0f; - sampler_info.minLod = 0.0f; - sampler_info.maxLod = 0.0f; + auto texture_manager::create_sampler() -> expected_t { + vk::SamplerCreateInfo sampler_info{}; + sampler_info.magFilter = vk::Filter::eLinear; + sampler_info.minFilter = vk::Filter::eLinear; + sampler_info.addressModeU = vk::SamplerAddressMode::eRepeat; + sampler_info.addressModeV = vk::SamplerAddressMode::eRepeat; + sampler_info.addressModeW = vk::SamplerAddressMode::eRepeat; + sampler_info.anisotropyEnable = VK_FALSE; + sampler_info.maxAnisotropy = 1.0f; + sampler_info.borderColor = vk::BorderColor::eIntOpaqueBlack; + sampler_info.unnormalizedCoordinates = VK_FALSE; + sampler_info.compareEnable = VK_FALSE; + sampler_info.compareOp = vk::CompareOp::eAlways; + sampler_info.mipmapMode = vk::SamplerMipmapMode::eLinear; + sampler_info.mipLodBias = 0.0f; + sampler_info.minLod = 0.0f; + sampler_info.maxLod = 0.0f; - auto [result, sampler] = device_.get_handle().createSampler(sampler_info); - if (result != vk::Result::eSuccess) - { - return std::unexpected(result); - } + auto [result, sampler] = device_.get_handle().createSampler(sampler_info); + if (result != vk::Result::eSuccess) { + return std::unexpected(result); + } - return sampler; - } + return sampler; + } - auto texture_manager::load_texture(std::string_view path) -> expected_t - { - // 检查缓存 - std::string path_str(path); - auto cache_it = path_cache_.find(path_str); - if (cache_it != path_cache_.end()) - { - return cache_it->second; - } + auto texture_manager::load_texture(std::string_view path) -> expected_t { + // 检查缓存 + std::string path_str(path); + auto cache_it = path_cache_.find(path_str); + if (cache_it != path_cache_.end()) { + return cache_it->second; + } - // 加载图片数据 - auto img_result = image_loader::load_from_file(path); - if (!img_result) - { - return std::unexpected(vk::Result::eErrorInitializationFailed); - } + // 加载图片数据 + auto img_result = image_loader::load_from_file(path); + if (!img_result) { + return std::unexpected(vk::Result::eErrorInitializationFailed); + } - // 创建纹理 - auto tex_result = create_texture(img_result.value(), path); - if (!tex_result) - { - return tex_result; - } + // 创建纹理 + auto tex_result = create_texture(img_result.value(), path); + if (!tex_result) { + return tex_result; + } - // 缓存路径 - texture_id_t id = tex_result.value(); - path_cache_[path_str] = id; + // 缓存路径 + texture_id_t id = tex_result.value(); + path_cache_[path_str] = id; - return id; - } + return id; + } - auto texture_manager::create_texture(const image_data &data, std::string_view name) - -> expected_t - { + auto texture_manager::create_texture(const image_data& data, std::string_view name) + -> expected_t { + // 创建 Vulkan 图像 + auto img_result = res_mgr_.create_image( + static_cast(data.width), + static_cast(data.height), + vk::Format::eR8G8B8A8Srgb, // 使用 sRGB 格式,GPU 自动处理 sRGB→linear 转换 + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal); - // 创建 Vulkan 图像 - auto img_result = res_mgr_.create_image( - static_cast(data.width), - static_cast(data.height), - vk::Format::eR8G8B8A8Srgb, // 使用 sRGB 格式,GPU 自动处理 sRGB→linear 转换 - vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, - vk::MemoryPropertyFlagBits::eDeviceLocal); + if (!img_result) { + return std::unexpected(img_result.error()); + } - if (!img_result) - { - return std::unexpected(img_result.error()); - } + auto& img_resource = img_result.value(); - auto &img_resource = img_result.value(); + // 上传纹理数据 + auto upload_result = upload_texture_data(img_resource.image, data); + if (!upload_result) { + res_mgr_.destroy_image(img_resource); + return std::unexpected(upload_result.error()); + } - // 上传纹理数据 - auto upload_result = upload_texture_data(img_resource.image, data); - if (!upload_result) - { - res_mgr_.destroy_image(img_resource); - return std::unexpected(upload_result.error()); - } + // 分配纹理 ID + texture_id_t id = next_id_++; - // 分配纹理 ID - texture_id_t id = next_id_++; + // 存储纹理信息 - 使用新的 resource 结构 + texture_info info{}; + info.resource = img_resource; // 直接存储完整的 image_resource + info.sampler = default_sampler_; + info.width = static_cast(data.width); + info.height = static_cast(data.height); - // 存储纹理信息 - 使用新的 resource 结构 - texture_info info{}; - info.resource = img_resource; // 直接存储完整的 image_resource - info.sampler = default_sampler_; - info.width = static_cast(data.width); - info.height = static_cast(data.height); + textures_[id] = info; - textures_[id] = info; + return id; + } - return id; - } + auto texture_manager::create_solid_texture(uint32_t rgba_color, int size) -> texture_id_t { + auto img_data = image_loader::create_solid_color(size, size, rgba_color); + auto result = create_texture(img_data, "solid_color"); + return result.value_or(INVALID_TEXTURE_ID); + } - auto texture_manager::create_solid_texture(uint32_t rgba_color, int size) -> texture_id_t - { - auto img_data = image_loader::create_solid_color(size, size, rgba_color); - auto result = create_texture(img_data, "solid_color"); - return result.value_or(INVALID_TEXTURE_ID); - } + auto texture_manager::get_texture(texture_id_t id) const -> const texture_info* { + auto it = textures_.find(id); + if (it != textures_.end()) { + return &it->second; + } + return nullptr; + } - auto texture_manager::get_texture(texture_id_t id) const -> const texture_info * - { - auto it = textures_.find(id); - if (it != textures_.end()) - { - return &it->second; - } - return nullptr; - } + void texture_manager::release_texture(texture_id_t id) { + auto it = textures_.find(id); + if (it == textures_.end()) { + return; + } - void texture_manager::release_texture(texture_id_t id) - { - auto it = textures_.find(id); - if (it == textures_.end()) - { - return; - } + // 直接销毁存储的 image_resource + res_mgr_.destroy_image(it->second.resource); - // 直接销毁存储的 image_resource - res_mgr_.destroy_image(it->second.resource); + // 从缓存中移除 + for (auto cache_it = path_cache_.begin(); cache_it != path_cache_.end();) { + if (cache_it->second == id) { + cache_it = path_cache_.erase(cache_it); + } + else { + ++cache_it; + } + } - // 从缓存中移除 - for (auto cache_it = path_cache_.begin(); cache_it != path_cache_.end();) - { - if (cache_it->second == id) - { - cache_it = path_cache_.erase(cache_it); - } - else - { - ++cache_it; - } - } + textures_.erase(it); + } - textures_.erase(it); - } + void texture_manager::release_all() { + // 释放所有纹理 + for (auto& [id, info] : textures_) { + res_mgr_.destroy_image(info.resource); + } - void texture_manager::release_all() - { - // 释放所有纹理 - for (auto &[id, info] : textures_) - { - res_mgr_.destroy_image(info.resource); - } + textures_.clear(); + path_cache_.clear(); + } - textures_.clear(); - path_cache_.clear(); - } + auto texture_manager::get_default_sampler() const -> vk::Sampler { + return default_sampler_; + } - auto texture_manager::get_default_sampler() const -> vk::Sampler - { - return default_sampler_; - } + void texture_manager::transition_image_layout( + vk::CommandBuffer cmd_buffer, + vk::Image image, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout) { + vk::ImageMemoryBarrier barrier{}; + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; - void texture_manager::transition_image_layout( - vk::CommandBuffer cmd_buffer, - vk::Image image, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout) - { - vk::ImageMemoryBarrier barrier{}; - barrier.oldLayout = old_layout; - barrier.newLayout = new_layout; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = image; - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; + vk::PipelineStageFlags source_stage; + vk::PipelineStageFlags destination_stage; - vk::PipelineStageFlags source_stage; - vk::PipelineStageFlags destination_stage; + if (old_layout == vk::ImageLayout::eUndefined && + new_layout == vk::ImageLayout::eTransferDstOptimal) { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - if (old_layout == vk::ImageLayout::eUndefined && - new_layout == vk::ImageLayout::eTransferDstOptimal) - { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + source_stage = vk::PipelineStageFlagBits::eTopOfPipe; + destination_stage = vk::PipelineStageFlagBits::eTransfer; + } + else if (old_layout == vk::ImageLayout::eTransferDstOptimal && + new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - source_stage = vk::PipelineStageFlagBits::eTopOfPipe; - destination_stage = vk::PipelineStageFlagBits::eTransfer; - } - else if (old_layout == vk::ImageLayout::eTransferDstOptimal && - new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) - { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + source_stage = vk::PipelineStageFlagBits::eTransfer; + destination_stage = vk::PipelineStageFlagBits::eFragmentShader; + } + else { + // 不支持的布局转换 + return; + } - source_stage = vk::PipelineStageFlagBits::eTransfer; - destination_stage = vk::PipelineStageFlagBits::eFragmentShader; - } - else - { - // 不支持的布局转换 - return; - } + cmd_buffer.pipelineBarrier( + source_stage, destination_stage, + {}, + 0, nullptr, + 0, nullptr, + 1, &barrier); + } - cmd_buffer.pipelineBarrier( - source_stage, destination_stage, - {}, - 0, nullptr, - 0, nullptr, - 1, &barrier); - } + auto texture_manager::upload_texture_data(vk::Image image, const image_data& data) + -> expected_t { + size_t data_size = data.pixels.size(); - auto texture_manager::upload_texture_data(vk::Image image, const image_data &data) - -> expected_t - { + // 创建 staging buffer (VMA 会自动映射 HOST_VISIBLE buffer) + auto staging_result = res_mgr_.create_buffer( + data_size, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); - size_t data_size = data.pixels.size(); + if (!staging_result) { + return std::unexpected(staging_result.error()); + } - // 创建 staging buffer (VMA 会自动映射 HOST_VISIBLE buffer) - auto staging_result = res_mgr_.create_buffer( - data_size, - vk::BufferUsageFlagBits::eTransferSrc, - vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); + auto& staging = staging_result.value(); - if (!staging_result) - { - return std::unexpected(staging_result.error()); - } + // 使用 VMA 已映射的内存指针,无需手动 mapMemory + if (!staging.mapped_data) { + res_mgr_.destroy_buffer(staging); + return std::unexpected(vk::Result::eErrorMemoryMapFailed); + } - auto &staging = staging_result.value(); + std::memcpy(staging.mapped_data, data.pixels.data(), data_size); + // 无需 unmapMemory,VMA 会在 destroy_buffer 时自动处理 - // 使用 VMA 已映射的内存指针,无需手动 mapMemory - if (!staging.mapped_data) - { - res_mgr_.destroy_buffer(staging); - return std::unexpected(vk::Result::eErrorMemoryMapFailed); - } + // 创建一次性命令缓冲区 + command_buffer cmd_buf(device_, device_.get_command_pool()); + auto alloc_result = cmd_buf.allocate(); + if (alloc_result != vk::Result::eSuccess) { + res_mgr_.destroy_buffer(staging); + return std::unexpected(alloc_result); + } - std::memcpy(staging.mapped_data, data.pixels.data(), data_size); - // 无需 unmapMemory,VMA 会在 destroy_buffer 时自动处理 + auto begin_result = cmd_buf.begin_recording(); + if (begin_result != vk::Result::eSuccess) { + res_mgr_.destroy_buffer(staging); + return std::unexpected(begin_result); + } - // 创建一次性命令缓冲区 - command_buffer cmd_buf(device_, device_.get_command_pool()); - auto alloc_result = cmd_buf.allocate(); - if (alloc_result != vk::Result::eSuccess) - { - res_mgr_.destroy_buffer(staging); - return std::unexpected(alloc_result); - } + auto cmd_handle = cmd_buf.get_handle(); - auto begin_result = cmd_buf.begin_recording(); - if (begin_result != vk::Result::eSuccess) - { - res_mgr_.destroy_buffer(staging); - return std::unexpected(begin_result); - } + // 转换图像布局:UNDEFINED -> TRANSFER_DST_OPTIMAL + transition_image_layout( + cmd_handle, + image, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eTransferDstOptimal); - auto cmd_handle = cmd_buf.get_handle(); + // 复制 buffer 到 image + vk::BufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = vk::Offset3D{0, 0, 0}; + region.imageExtent = vk::Extent3D{ + static_cast(data.width), + static_cast(data.height), + 1 + }; - // 转换图像布局:UNDEFINED -> TRANSFER_DST_OPTIMAL - transition_image_layout( - cmd_handle, - image, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eTransferDstOptimal); + cmd_handle.copyBufferToImage( + staging.buffer, + image, + vk::ImageLayout::eTransferDstOptimal, + 1, + ®ion); - // 复制 buffer 到 image - vk::BufferImageCopy region{}; - region.bufferOffset = 0; - region.bufferRowLength = 0; - region.bufferImageHeight = 0; - region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; - region.imageSubresource.mipLevel = 0; - region.imageSubresource.baseArrayLayer = 0; - region.imageSubresource.layerCount = 1; - region.imageOffset = vk::Offset3D{0, 0, 0}; - region.imageExtent = vk::Extent3D{ - static_cast(data.width), - static_cast(data.height), - 1}; + // 转换图像布局:TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL + transition_image_layout( + cmd_handle, + image, + vk::ImageLayout::eTransferDstOptimal, + vk::ImageLayout::eShaderReadOnlyOptimal); - cmd_handle.copyBufferToImage( - staging.buffer, - image, - vk::ImageLayout::eTransferDstOptimal, - 1, - ®ion); + auto end_result = cmd_buf.end_recording(); + if (end_result != vk::Result::eSuccess) { + res_mgr_.destroy_buffer(staging); + return std::unexpected(end_result); + } - // 转换图像布局:TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL - transition_image_layout( - cmd_handle, - image, - vk::ImageLayout::eTransferDstOptimal, - vk::ImageLayout::eShaderReadOnlyOptimal); + // 提交命令并等待完成 + auto submit_result = cmd_buf.submit(); + if (submit_result != vk::Result::eSuccess) { + res_mgr_.destroy_buffer(staging); + return std::unexpected(submit_result); + } - auto end_result = cmd_buf.end_recording(); - if (end_result != vk::Result::eSuccess) - { - res_mgr_.destroy_buffer(staging); - return std::unexpected(end_result); - } + // 等待队列空闲 + auto wait_result = device_.get_graphics_queue().waitIdle(); + if (wait_result != vk::Result::eSuccess) { + res_mgr_.destroy_buffer(staging); + return std::unexpected(wait_result); + } - // 提交命令并等待完成 - auto submit_result = cmd_buf.submit(); - if (submit_result != vk::Result::eSuccess) - { - res_mgr_.destroy_buffer(staging); - return std::unexpected(submit_result); - } + // 清理 staging buffer + res_mgr_.destroy_buffer(staging); - // 等待队列空闲 - auto wait_result = device_.get_graphics_queue().waitIdle(); - if (wait_result != vk::Result::eSuccess) - { - res_mgr_.destroy_buffer(staging); - return std::unexpected(wait_result); - } - - // 清理 staging buffer - res_mgr_.destroy_buffer(staging); - - return {}; - } - -} // namespace mirage \ No newline at end of file + return {}; + } +} // namespace mirage diff --git a/src/render/image/texture_manager.h b/src/render/image/texture_manager.h index 7186d88..5b616ed 100644 --- a/src/render/image/texture_manager.h +++ b/src/render/image/texture_manager.h @@ -8,100 +8,96 @@ #include #include -namespace mirage -{ - /// @brief 纹理信息 - struct texture_info - { - resource_manager::image_resource resource{}; // VMA 管理的图像资源 - vk::Sampler sampler{VK_NULL_HANDLE}; - uint32_t width{0}; - uint32_t height{0}; +namespace mirage { + /// @brief 纹理信息 + struct texture_info { + resource_manager::image_resource resource{}; // VMA 管理的图像资源 + vk::Sampler sampler{VK_NULL_HANDLE}; + uint32_t width{0}; + uint32_t height{0}; - // 便捷访问器 - [[nodiscard]] auto image() const { return resource.image; } - [[nodiscard]] auto image_view() const { return resource.view; } - [[nodiscard]] auto size() const { return vec2i_t{static_cast(width), static_cast(height)}; } - }; + // 便捷访问器 + [[nodiscard]] auto image() const { return resource.image; } + [[nodiscard]] auto image_view() const { return resource.view; } + [[nodiscard]] auto size() const { return vec2i_t{static_cast(width), static_cast(height)}; } + }; - /// @brief 纹理管理器 - /// - /// 负责创建和管理 Vulkan 纹理资源。 - /// 提供纹理缓存功能,避免重复加载相同的图片。 - class texture_manager - { - public: - explicit texture_manager(resource_manager &res_mgr, const logical_device &device); - ~texture_manager(); + /// @brief 纹理管理器 + /// + /// 负责创建和管理 Vulkan 纹理资源。 + /// 提供纹理缓存功能,避免重复加载相同的图片。 + class texture_manager { + public: + explicit texture_manager(resource_manager& res_mgr, const logical_device& device); + ~texture_manager(); - // 禁止拷贝 - texture_manager(const texture_manager &) = delete; - texture_manager &operator=(const texture_manager &) = delete; + // 禁止拷贝 + texture_manager(const texture_manager&) = delete; + texture_manager& operator=(const texture_manager&) = delete; - /// @brief 从文件加载纹理(带缓存) - /// @param path 文件路径 - /// @return 纹理 ID,失败时返回错误 - [[nodiscard]] auto load_texture(std::string_view path) -> expected_t; + /// @brief 从文件加载纹理(带缓存) + /// @param path 文件路径 + /// @return 纹理 ID,失败时返回错误 + [[nodiscard]] auto load_texture(std::string_view path) -> expected_t; - /// @brief 从 image_data 创建纹理 - /// @param data 图片数据 - /// @param name 纹理名称(可选,用于调试) - /// @return 纹理 ID,失败时返回错误 - [[nodiscard]] auto create_texture(const image_data &data, std::string_view name = "") - -> expected_t; + /// @brief 从 image_data 创建纹理 + /// @param data 图片数据 + /// @param name 纹理名称(可选,用于调试) + /// @return 纹理 ID,失败时返回错误 + [[nodiscard]] auto create_texture(const image_data& data, std::string_view name = "") + -> expected_t; - /// @brief 创建纯色纹理 - /// @param rgba_color RGBA 颜色值(格式:0xRRGGBBAA) - /// @param size 纹理大小(默认 1x1) - /// @return 纹理 ID - [[nodiscard]] auto create_solid_texture(uint32_t rgba_color, int size = 1) -> texture_id_t; + /// @brief 创建纯色纹理 + /// @param rgba_color RGBA 颜色值(格式:0xRRGGBBAA) + /// @param size 纹理大小(默认 1x1) + /// @return 纹理 ID + [[nodiscard]] auto create_solid_texture(uint32_t rgba_color, int size = 1) -> texture_id_t; - /// @brief 获取纹理信息 - /// @param id 纹理 ID - /// @return 纹理信息指针(无效 ID 返回 nullptr) - [[nodiscard]] auto get_texture(texture_id_t id) const -> const texture_info *; + /// @brief 获取纹理信息 + /// @param id 纹理 ID + /// @return 纹理信息指针(无效 ID 返回 nullptr) + [[nodiscard]] auto get_texture(texture_id_t id) const -> const texture_info*; - /// @brief 释放纹理 - /// @param id 纹理 ID - void release_texture(texture_id_t id); + /// @brief 释放纹理 + /// @param id 纹理 ID + void release_texture(texture_id_t id); - /// @brief 释放所有纹理 - void release_all(); + /// @brief 释放所有纹理 + void release_all(); - /// @brief 获取默认采样器 - /// @return 默认采样器句柄 - [[nodiscard]] auto get_default_sampler() const -> vk::Sampler; + /// @brief 获取默认采样器 + /// @return 默认采样器句柄 + [[nodiscard]] auto get_default_sampler() const -> vk::Sampler; - private: - resource_manager &res_mgr_; - const logical_device &device_; - std::unordered_map path_cache_; - std::unordered_map textures_; - texture_id_t next_id_{1}; - vk::Sampler default_sampler_{VK_NULL_HANDLE}; + private: + resource_manager& res_mgr_; + const logical_device& device_; + std::unordered_map path_cache_; + std::unordered_map textures_; + texture_id_t next_id_{1}; + vk::Sampler default_sampler_{VK_NULL_HANDLE}; - /// @brief 创建采样器 - /// @return 采样器句柄,失败时返回错误 - [[nodiscard]] auto create_sampler() -> expected_t; + /// @brief 创建采样器 + /// @return 采样器句柄,失败时返回错误 + [[nodiscard]] auto create_sampler() -> expected_t; - /// @brief 上传图像数据到 GPU - /// @param image 目标图像 - /// @param data 图片数据 - /// @return void 或错误 - [[nodiscard]] auto upload_texture_data( - vk::Image image, - const image_data &data) -> expected_t; + /// @brief 上传图像数据到 GPU + /// @param image 目标图像 + /// @param data 图片数据 + /// @return void 或错误 + [[nodiscard]] auto upload_texture_data( + vk::Image image, + const image_data& data) -> expected_t; - /// @brief 转换图像布局 - /// @param cmd_buffer 命令缓冲区 - /// @param image 图像 - /// @param old_layout 旧布局 - /// @param new_layout 新布局 - void transition_image_layout( - vk::CommandBuffer cmd_buffer, - vk::Image image, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout); - }; - -} // namespace mirage \ No newline at end of file + /// @brief 转换图像布局 + /// @param cmd_buffer 命令缓冲区 + /// @param image 图像 + /// @param old_layout 旧布局 + /// @param new_layout 新布局 + void transition_image_layout( + vk::CommandBuffer cmd_buffer, + vk::Image image, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout); + }; +} // namespace mirage diff --git a/src/render/pipeline/render_pipeline.cpp b/src/render/pipeline/render_pipeline.cpp index bbc4666..6671810 100644 --- a/src/render/pipeline/render_pipeline.cpp +++ b/src/render/pipeline/render_pipeline.cpp @@ -14,11 +14,15 @@ namespace mirage { swapchain& swapchain, resource_manager& res_mgr, texture_manager& tex_mgr, + render::text::text_shaper& text_shaper, + render::text::glyph_cache& glyph_cache, const config& cfg ) : device_(device) , swapchain_(swapchain) , res_mgr_(res_mgr) , tex_mgr_(tex_mgr) + , text_shaper_(text_shaper) + , glyph_cache_(glyph_cache) , config_(cfg) { // 子组件将在 initialize() 中创建 } @@ -38,8 +42,8 @@ namespace mirage { auto extent = swapchain_.get_extent(); - // 1. 创建渲染树构建器 - tree_builder_ = std::make_unique(); + // 1. 创建渲染树构建器(传递文本渲染依赖) + tree_builder_ = std::make_unique(text_shaper_, glyph_cache_); // 2. 创建渲染树执行器 tree_executor_ = std::make_unique(); @@ -108,8 +112,20 @@ namespace mirage { } ); imager_->initialize(offscreen_->render_pass(false)); - - // 8. 创建遮罩渲染器 + + // 8. 创建文本渲染器 + text_ = std::make_unique( + device_, + res_mgr_, + glyph_cache_, + render::text_renderer::config{ + .initial_vertex_capacity = 4096, + .initial_index_capacity = 6144 + } + ); + text_->initialize(offscreen_->render_pass(false)); + + // 9. 创建遮罩渲染器 mask_ = std::make_unique( device_, res_mgr_, @@ -121,13 +137,13 @@ namespace mirage { // 注入统一渲染目标池后再初始化 mask_->set_target_pool(target_pool_.get()); mask_->initialize(offscreen_->render_pass(false), extent); - - // 9. 创建后效应用器 + + // 10. 创建后效应用器 effects_ = std::make_unique(device_, res_mgr_); effects_->set_target_pool(target_pool_.get()); effects_->initialize(extent.width, extent.height, config_.frames_in_flight); - - // 10. 创建 Blit Pipeline + + // 11. 创建 Blit Pipeline create_blit_pipeline(); create_blit_descriptors(); update_blit_descriptor(); @@ -378,7 +394,7 @@ namespace mirage { // 2. 重置后效描述符池(在等待 fence 之后,传递帧索引) effects_->begin_frame(frame_index); - // 3. 构建渲染树 + // 3. 构建渲染树(tree_builder_ 已经包含了 text_shaper_ 和 glyph_cache_ 引用) auto tree = tree_builder_->build(commands); // 4. 开始各渲染器的帧 @@ -395,16 +411,21 @@ namespace mirage { static_cast(extent.width), static_cast(extent.height) }); - + text_->begin_frame(frame_index, vec2f_t{ + static_cast(extent.width), + static_cast(extent.height) + }); + // 5. 开始离屏渲染 Pass(idle -> offscreen_pass) auto offscreen_ctx = std::move(idle_ctx).begin_offscreen_pass(*offscreen_, true); - + // 6. 执行渲染树 render::executor_context exec_ctx{ .geometry = geometry_.get(), .image = imager_.get(), .mask = mask_.get(), .post_effect = effects_.get(), + .text = text_.get(), .frame_ctx = &offscreen_ctx, .time = time }; @@ -437,7 +458,8 @@ namespace mirage { geometry_->end_frame(); imager_->end_frame(frame_index); mask_->end_frame(); - + text_->end_frame(frame_index); + // 14. 完成帧,获取资源用于提交 auto resources = std::move(idle_ctx).finish(); @@ -501,7 +523,11 @@ namespace mirage { static_cast(extent.width), static_cast(extent.height) }); - + text_->begin_frame(frame_index, vec2f_t{ + static_cast(extent.width), + static_cast(extent.height) + }); + // 判断渲染模式:全量渲染 vs 增量渲染 bool use_full_render = need_full_render_; vk::Rect2D scissor_rect; @@ -582,6 +608,7 @@ namespace mirage { .image = imager_.get(), .mask = mask_.get(), .post_effect = effects_.get(), + .text = text_.get(), .frame_ctx = &offscreen_ctx, .time = time }; @@ -601,7 +628,8 @@ namespace mirage { geometry_->end_frame(); imager_->end_frame(frame_index); mask_->end_frame(); - + text_->end_frame(frame_index); + // 5. 准备 Blit 到 Swapchain // 确保离屏目标在 shader_read 状态 auto extent = swapchain_.get_extent(); @@ -763,10 +791,14 @@ namespace mirage { imager_->cleanup(); imager_->initialize(offscreen_->render_pass(false)); - - // 4. 重新初始化遮罩渲染器 + + // 4. 重新初始化文本渲染器 + text_->cleanup(); + text_->initialize(offscreen_->render_pass(false)); + + // 5. 重新初始化遮罩渲染器 mask_->resize(vk::Extent2D{width, height}); - + // 6. 更新后效应用器的尺寸和临时目标 effects_->resize(width, height); @@ -800,6 +832,7 @@ namespace mirage { geometry_.reset(); imager_.reset(); mask_.reset(); + text_.reset(); effects_.reset(); offscreen_.reset(); scheduler_.reset(); diff --git a/src/render/pipeline/render_pipeline.h b/src/render/pipeline/render_pipeline.h index a02aa82..fe41a1c 100644 --- a/src/render/pipeline/render_pipeline.h +++ b/src/render/pipeline/render_pipeline.h @@ -8,6 +8,7 @@ #include "geometry_renderer.h" #include "image_renderer.h" #include "mask_renderer.h" +#include "text_renderer.h" #include "post_effect_applicator.h" #include "frame_scheduler.h" #include "frame_context.h" @@ -22,6 +23,11 @@ #include #include +namespace mirage::render::text { + class text_shaper; + class glyph_cache; +} + namespace mirage { /// 渲染管线 - 整合所有组件的主入口 /// @@ -42,12 +48,16 @@ namespace mirage { /// @param swapchain 交换链 /// @param res_mgr 资源管理器 /// @param tex_mgr 纹理管理器 + /// @param text_shaper 文本排版器引用 + /// @param glyph_cache 字形缓存引用 /// @param cfg 配置选项 render_pipeline( logical_device& device, swapchain& swapchain, resource_manager& res_mgr, texture_manager& tex_mgr, + render::text::text_shaper& text_shaper, + render::text::glyph_cache& glyph_cache, const config& cfg = {} ); @@ -95,6 +105,8 @@ namespace mirage { swapchain& swapchain_; resource_manager& res_mgr_; texture_manager& tex_mgr_; + render::text::text_shaper& text_shaper_; + render::text::glyph_cache& glyph_cache_; config config_; // ========================================================================= @@ -107,6 +119,7 @@ namespace mirage { std::unique_ptr geometry_; std::unique_ptr imager_; std::unique_ptr mask_; + std::unique_ptr text_; std::unique_ptr effects_; std::unique_ptr scheduler_; diff --git a/src/render/pipeline/render_tree_builder.cpp b/src/render/pipeline/render_tree_builder.cpp index 1dd1aae..6bd825b 100644 --- a/src/render/pipeline/render_tree_builder.cpp +++ b/src/render/pipeline/render_tree_builder.cpp @@ -1,5 +1,7 @@ #include "render_tree_builder.h" #include "renderer/vertex_types.h" +#include "text/text_shaper.h" +#include "text/glyph_cache.h" #include #include #include @@ -7,6 +9,12 @@ namespace mirage::render { +render_tree_builder::render_tree_builder(text::text_shaper& shaper, text::glyph_cache& cache) + : text_shaper_(shaper) + , glyph_cache_(cache) +{ +} + namespace { // ============================================================================ @@ -116,8 +124,12 @@ namespace { batch.indices.push_back(base_index + 3); } - void append_command_to_batch(draw_batch& batch, const render_command& cmd) { - std::visit([&batch](const T& concrete_cmd) { + void append_command_to_batch( + draw_batch& batch, + const render_command& cmd, + text::text_shaper& text_shaper + ) { + std::visit([&batch, &text_shaper](const T& concrete_cmd) { using CmdType = std::decay_t; if constexpr (std::is_same_v) { @@ -181,7 +193,68 @@ namespace { append_quad(batch, params); } else if constexpr (std::is_same_v) { - // TODO: Implement text rendering + // 转换UTF-8文本为UTF-32 + auto utf32_text = text::text_shaper::utf8_to_utf32(concrete_cmd.text); + + // 使用text_shaper生成文本顶点数据 + auto shaped = text_shaper.shape_text( + utf32_text, + concrete_cmd.font_id, + concrete_cmd.font_size, + concrete_cmd.position, + concrete_cmd.text_color + ); + + // 标记为文本批次 + batch.is_text_batch = true; + batch.font_id = concrete_cmd.font_id; + + // 将生成的顶点和索引添加到批次 + const auto base_index = static_cast(batch.vertices.size()); + batch.vertices.insert( + batch.vertices.end(), + shaped.vertices.begin(), + shaped.vertices.end() + ); + + // 调整索引偏移 + for (const auto idx : shaped.indices) { + batch.indices.push_back(base_index + idx); + } + } + else if constexpr (std::is_same_v) { + // 转换UTF-8文本为UTF-32 + auto utf32_text = text::text_shaper::utf8_to_utf32(concrete_cmd.text); + + // 使用text_shaper生成文本顶点数据 + auto shaped = text_shaper.shape_text( + utf32_text, + concrete_cmd.font_id, + concrete_cmd.font_size, + concrete_cmd.position, + concrete_cmd.text_color + ); + + // 标记为文本批次 + batch.is_text_batch = true; + batch.font_id = concrete_cmd.font_id; + + // 将生成的顶点和索引添加到批次 + const auto base_index = static_cast(batch.vertices.size()); + batch.vertices.insert( + batch.vertices.end(), + shaped.vertices.begin(), + shaped.vertices.end() + ); + + // 调整索引偏移 + for (const auto idx : shaped.indices) { + batch.indices.push_back(base_index + idx); + } + + // 注意:effect_type和effect_params需要在渲染时处理 + // 当前将顶点数据添加到普通批次中 + // 未来可能需要扩展draw_batch结构以支持shader_id和effect参数 } }, cmd); } @@ -316,7 +389,7 @@ void render_tree_builder::process_geometry(const render_command& cmd, build_cont } if (same_texture && same_clip) { - append_command_to_batch(last_batch, cmd); + append_command_to_batch(last_batch, cmd, text_shaper_); merged = true; } } @@ -325,7 +398,7 @@ void render_tree_builder::process_geometry(const render_command& cmd, build_cont draw_batch new_batch; new_batch.texture_id = texture_id; new_batch.clip_rect = clip_rect; - append_command_to_batch(new_batch, cmd); + append_command_to_batch(new_batch, cmd, text_shaper_); geo_node->add_batch(std::move(new_batch)); } } diff --git a/src/render/pipeline/render_tree_builder.h b/src/render/pipeline/render_tree_builder.h index 6e0f6f0..52bdfd2 100644 --- a/src/render/pipeline/render_tree_builder.h +++ b/src/render/pipeline/render_tree_builder.h @@ -8,10 +8,21 @@ namespace mirage::render { +// 前向声明 +namespace text { + class text_shaper; + class glyph_cache; +} + /// @brief 渲染树构建器 /// 将线性的渲染命令列表转换为树形结构 class render_tree_builder { public: + /// @brief 构造函数 + /// @param shaper 文本排版器引用 + /// @param cache 字形缓存引用 + explicit render_tree_builder(text::text_shaper& shaper, text::glyph_cache& cache); + /// @brief 构建渲染树 /// @param commands 渲染命令列表 /// @return 构建好的渲染树 @@ -63,6 +74,10 @@ private: /// 如果当前是几何节点,则将其弹出栈,恢复到父容器 /// @param context 构建上下文 void ensure_not_geometry(build_context& context); + + // 文本渲染依赖 + text::text_shaper& text_shaper_; + text::glyph_cache& glyph_cache_; }; } // namespace mirage::render \ No newline at end of file diff --git a/src/render/pipeline/render_tree_executor.cpp b/src/render/pipeline/render_tree_executor.cpp index 48e163b..61a03e6 100644 --- a/src/render/pipeline/render_tree_executor.cpp +++ b/src/render/pipeline/render_tree_executor.cpp @@ -80,9 +80,19 @@ namespace mirage::render { if (node.get_batches().empty()) { return; } - // 根据批次是否有纹理ID选择渲染器 + // 根据批次类型选择渲染器 for (const auto& batch : node.get_batches()) { - if (batch.texture_id.has_value()) { + if (batch.is_text_batch) { + // 文本批次,使用文本渲染器 + if (ctx.text) { + render::text_batch text_batch; + text_batch.font_id = batch.font_id; + text_batch.vertices = batch.vertices; + text_batch.indices = batch.indices; + ctx.text->render(*ctx.frame_ctx, text_batch); + } + } + else if (batch.texture_id.has_value()) { // 有纹理ID,使用图片渲染器 ctx.image->render(*ctx.frame_ctx, batch); } @@ -223,6 +233,7 @@ namespace mirage::render { ctx.image, ctx.mask, ctx.post_effect, + ctx.text, ctx.target_pool, &mask_ctx, ctx.time @@ -256,6 +267,7 @@ namespace mirage::render { ctx.image, ctx.mask, ctx.post_effect, + ctx.text, ctx.target_pool, &inner_mask_ctx, ctx.time @@ -354,6 +366,7 @@ namespace mirage::render { ctx.image, ctx.mask, ctx.post_effect, + ctx.text, ctx.target_pool, &temp_pass_ctx, ctx.time @@ -414,6 +427,7 @@ namespace mirage::render { ctx.image, ctx.mask, ctx.post_effect, + ctx.text, ctx.target_pool, &temp_mask_ctx, ctx.time diff --git a/src/render/pipeline/render_tree_executor.h b/src/render/pipeline/render_tree_executor.h index e267839..3a6aa34 100644 --- a/src/render/pipeline/render_tree_executor.h +++ b/src/render/pipeline/render_tree_executor.h @@ -6,6 +6,7 @@ #include "image_renderer.h" #include "mask_renderer.h" #include "post_effect_applicator.h" +#include "text_renderer.h" #include "frame_context.h" namespace mirage::render { @@ -18,6 +19,7 @@ namespace mirage::render { image_renderer* image; ///< 图片渲染器 mask_renderer* mask; ///< 遮罩渲染器 post_effect_applicator* post_effect; ///< 后效应用器 + text_renderer* text; ///< 文本渲染器 unified_target_pool* target_pool; ///< 统一渲染目标池(可为空) pass_state::frame_context* frame_ctx; ///< 当前帧上下文 float time = 0.0f; ///< 当前时间(用于后效) diff --git a/src/render/pipeline/text_renderer.cpp b/src/render/pipeline/text_renderer.cpp new file mode 100644 index 0000000..93270e7 --- /dev/null +++ b/src/render/pipeline/text_renderer.cpp @@ -0,0 +1,995 @@ +#include "text_renderer.h" +#include "vulkan/pipeline/graphics_pipeline.h" +#include "renderer/uniform_types.h" +#include +#include +#include + +// 引入着色器 SPIR-V 数据 +#include "mtsdf_text_vert_frag.h" + +namespace mirage::render { + +text_renderer::text_renderer( + logical_device& device, + resource_manager& res_mgr, + text::glyph_cache& glyph_cache, + const config& cfg +) + : device_(device) + , res_mgr_(res_mgr) + , glyph_cache_(glyph_cache) + , config_(cfg) +{ +} + +text_renderer::~text_renderer() { + cleanup(); +} + +void text_renderer::initialize(vk::RenderPass render_pass) { + create_descriptor_set_layouts(); + create_descriptor_pool(); + create_frame_resources(); + + // 加载标准MTSDF着色器并创建管线 + auto frag_spirv = std::span(shaders::mtsdf_text_frag_spirv); + standard_pipeline_ = create_pipeline(render_pass, frag_spirv); + + // 创建图集采样器 + auto device_handle = device_.get_handle(); + vk::SamplerCreateInfo sampler_info{}; + sampler_info.magFilter = vk::Filter::eLinear; + sampler_info.minFilter = vk::Filter::eLinear; + sampler_info.addressModeU = vk::SamplerAddressMode::eClampToEdge; + sampler_info.addressModeV = vk::SamplerAddressMode::eClampToEdge; + sampler_info.addressModeW = vk::SamplerAddressMode::eClampToEdge; + sampler_info.anisotropyEnable = VK_FALSE; + sampler_info.maxAnisotropy = 1.0f; + sampler_info.borderColor = vk::BorderColor::eFloatTransparentBlack; + sampler_info.unnormalizedCoordinates = VK_FALSE; + sampler_info.compareEnable = VK_FALSE; + sampler_info.mipmapMode = vk::SamplerMipmapMode::eLinear; + + auto [sampler_result, sampler] = device_handle.createSampler(sampler_info); + if (sampler_result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create atlas sampler for text renderer"); + } + atlas_sampler_ = sampler; +} + +void text_renderer::create_descriptor_set_layouts() { + auto device_handle = device_.get_handle(); + + // 创建图集纹理描述符集布局 (set 0) + vk::DescriptorSetLayoutBinding atlas_binding{}; + atlas_binding.binding = 0; + atlas_binding.descriptorType = vk::DescriptorType::eCombinedImageSampler; + atlas_binding.descriptorCount = 1; + atlas_binding.stageFlags = vk::ShaderStageFlagBits::eFragment; + + vk::DescriptorSetLayoutCreateInfo atlas_layout_info{}; + atlas_layout_info.bindingCount = 1; + atlas_layout_info.pBindings = &atlas_binding; + + auto [atlas_result, atlas_layout] = device_handle.createDescriptorSetLayout(atlas_layout_info); + if (atlas_result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create atlas descriptor set layout"); + } + atlas_set_layout_ = atlas_layout; + + // 创建着色器参数描述符集布局 (set 1) + vk::DescriptorSetLayoutBinding params_binding{}; + params_binding.binding = 0; + params_binding.descriptorType = vk::DescriptorType::eUniformBuffer; + params_binding.descriptorCount = 1; + params_binding.stageFlags = vk::ShaderStageFlagBits::eFragment; + + vk::DescriptorSetLayoutCreateInfo params_layout_info{}; + params_layout_info.bindingCount = 1; + params_layout_info.pBindings = ¶ms_binding; + + auto [params_result, params_layout] = device_handle.createDescriptorSetLayout(params_layout_info); + if (params_result != vk::Result::eSuccess) { + device_handle.destroyDescriptorSetLayout(atlas_set_layout_); + throw std::runtime_error("Failed to create params descriptor set layout"); + } + params_set_layout_ = params_layout; +} + +void text_renderer::create_descriptor_pool() { + auto device_handle = device_.get_handle(); + + descriptor_pools_.resize(MAX_FRAMES_IN_FLIGHT); + + std::array pool_sizes{}; + pool_sizes[0].type = vk::DescriptorType::eCombinedImageSampler; + pool_sizes[0].descriptorCount = 8; // 每帧最多8个图集描述符 + pool_sizes[1].type = vk::DescriptorType::eUniformBuffer; + pool_sizes[1].descriptorCount = 8; // 每帧最多8个参数缓冲 + + vk::DescriptorPoolCreateInfo pool_info{}; + pool_info.poolSizeCount = static_cast(pool_sizes.size()); + pool_info.pPoolSizes = pool_sizes.data(); + pool_info.maxSets = 16; // 每帧最多16个描述符集 + + for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + auto [pool_result, pool] = device_handle.createDescriptorPool(pool_info); + if (pool_result != vk::Result::eSuccess) { + for (uint32_t j = 0; j < i; ++j) { + device_handle.destroyDescriptorPool(descriptor_pools_[j]); + } + descriptor_pools_.clear(); + throw std::runtime_error("Failed to create descriptor pool for frame " + std::to_string(i)); + } + descriptor_pools_[i] = pool; + } +} + +void text_renderer::create_frame_resources() { + auto device_handle = device_.get_handle(); + + frame_data_.reserve(MAX_FRAMES_IN_FLIGHT); + + for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + // 创建顶点缓冲 + auto vb_result = res_mgr_.create_buffer( + config_.initial_vertex_capacity * sizeof(ui_vertex), + vk::BufferUsageFlagBits::eVertexBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent + ); + + if (!vb_result) { + throw std::runtime_error("Failed to create vertex buffer: " + vk::to_string(vb_result.error())); + } + + // 创建索引缓冲 + auto ib_result = res_mgr_.create_buffer( + config_.initial_index_capacity * sizeof(uint32_t), + vk::BufferUsageFlagBits::eIndexBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent + ); + + if (!ib_result) { + res_mgr_.destroy_buffer(vb_result.value()); + throw std::runtime_error("Failed to create index buffer: " + vk::to_string(ib_result.error())); + } + + // 创建参数uniform缓冲 + auto pb_result = res_mgr_.create_buffer( + sizeof(text_shader_params), + vk::BufferUsageFlagBits::eUniformBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent + ); + + if (!pb_result) { + res_mgr_.destroy_buffer(vb_result.value()); + res_mgr_.destroy_buffer(ib_result.value()); + throw std::runtime_error("Failed to create params buffer: " + vk::to_string(pb_result.error())); + } + + // 创建typed_buffer包装器 + frame_data_.emplace_back(frame_data{ + typed_buffer(device_handle, std::move(vb_result.value()), config_.initial_vertex_capacity), + typed_buffer(device_handle, std::move(ib_result.value()), config_.initial_index_capacity), + typed_buffer(device_handle, std::move(pb_result.value()), 1) + }); + } +} + +auto text_renderer::create_pipeline( + vk::RenderPass render_pass, + std::span frag_spirv +) -> vk::Pipeline { + auto device_handle = device_.get_handle(); + + // 加载顶点着色器 + auto vert_shader_result = graphics_pipeline::load_shader_module_from_memory( + device_, shaders::mtsdf_text_vert_spirv); + if (!vert_shader_result) { + throw std::runtime_error("Failed to load vertex shader for text renderer"); + } + auto vert_shader = vert_shader_result.value(); + + // 加载片段着色器 + auto frag_shader_result = graphics_pipeline::load_shader_module_from_memory( + device_, frag_spirv); + if (!frag_shader_result) { + device_handle.destroyShaderModule(vert_shader); + throw std::runtime_error("Failed to load fragment shader for text renderer"); + } + auto frag_shader = frag_shader_result.value(); + + // 定义Push Constants范围 + vk::PushConstantRange push_constant_range{ + vk::ShaderStageFlagBits::eVertex, + 0, + sizeof(push_constants) + }; + + // 创建Pipeline Layout + std::array layouts = { + atlas_set_layout_, + params_set_layout_ + }; + + vk::PipelineLayoutCreateInfo pipeline_layout_info{ + {}, + static_cast(layouts.size()), layouts.data(), + 1, &push_constant_range + }; + + // 如果pipeline_layout_还未创建,创建它 + if (!pipeline_layout_) { + auto [result_layout, pipeline_layout] = device_handle.createPipelineLayout(pipeline_layout_info); + if (result_layout != vk::Result::eSuccess) { + device_handle.destroyShaderModule(vert_shader); + device_handle.destroyShaderModule(frag_shader); + throw std::runtime_error("Failed to create pipeline layout for text renderer"); + } + pipeline_layout_ = pipeline_layout; + } + + // 获取顶点输入描述 + auto binding_desc = ui_vertex::get_binding_description(); + auto attribute_descs = ui_vertex::get_attribute_descriptions(); + auto vertex_input_info = ui_vertex::get_vertex_input_state(binding_desc, attribute_descs); + + // 输入装配状态 + vk::PipelineInputAssemblyStateCreateInfo input_assembly{ + {}, + vk::PrimitiveTopology::eTriangleList, + VK_FALSE + }; + + // 视口和裁剪(动态状态) + vk::Viewport viewport{0.0f, 0.0f, 800.0f, 600.0f, 0.0f, 1.0f}; + vk::Rect2D scissor{{0, 0}, {800, 600}}; + + vk::PipelineViewportStateCreateInfo viewport_state{ + {}, + 1, &viewport, + 1, &scissor + }; + + // 光栅化状态 + vk::PipelineRasterizationStateCreateInfo rasterizer{ + {}, + VK_FALSE, + VK_FALSE, + vk::PolygonMode::eFill, + vk::CullModeFlagBits::eNone, + vk::FrontFace::eCounterClockwise, + VK_FALSE, + 0.0f, 0.0f, 0.0f, + 1.0f + }; + + // 多重采样状态 + vk::PipelineMultisampleStateCreateInfo multisampling{ + {}, + vk::SampleCountFlagBits::e1, + VK_FALSE + }; + + // 颜色混合附件(Alpha混合) + vk::PipelineColorBlendAttachmentState color_blend_attachment{ + VK_TRUE, + vk::BlendFactor::eSrcAlpha, + vk::BlendFactor::eOneMinusSrcAlpha, + vk::BlendOp::eAdd, + vk::BlendFactor::eOne, + vk::BlendFactor::eZero, + vk::BlendOp::eAdd, + vk::ColorComponentFlagBits::eR | + vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | + vk::ColorComponentFlagBits::eA + }; + + vk::PipelineColorBlendStateCreateInfo color_blending{ + {}, + VK_FALSE, + vk::LogicOp::eCopy, + 1, + &color_blend_attachment + }; + + // 深度模板状态 + vk::PipelineDepthStencilStateCreateInfo depth_stencil{}; + depth_stencil.depthTestEnable = VK_FALSE; + depth_stencil.depthWriteEnable = VK_FALSE; + depth_stencil.stencilTestEnable = VK_FALSE; + + // 动态状态 + std::vector dynamic_states = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor + }; + + vk::PipelineDynamicStateCreateInfo dynamic_state{ + {}, + static_cast(dynamic_states.size()), + dynamic_states.data() + }; + + // 着色器阶段 + vk::PipelineShaderStageCreateInfo vert_stage{ + {}, + vk::ShaderStageFlagBits::eVertex, + vert_shader, + "main" + }; + + vk::PipelineShaderStageCreateInfo frag_stage{ + {}, + vk::ShaderStageFlagBits::eFragment, + frag_shader, + "main" + }; + + std::array shader_stages = {vert_stage, frag_stage}; + + // 创建图形管线 + vk::GraphicsPipelineCreateInfo pipeline_info{ + {}, + static_cast(shader_stages.size()), + shader_stages.data(), + &vertex_input_info, + &input_assembly, + nullptr, + &viewport_state, + &rasterizer, + &multisampling, + &depth_stencil, + &color_blending, + &dynamic_state, + pipeline_layout_, + render_pass, + 0 + }; + + auto [result_pipeline, pipeline] = device_handle.createGraphicsPipeline(nullptr, pipeline_info); + if (result_pipeline != vk::Result::eSuccess) { + device_handle.destroyShaderModule(vert_shader); + device_handle.destroyShaderModule(frag_shader); + throw std::runtime_error("Failed to create graphics pipeline for text renderer"); + } + + // 清理着色器模块 + device_handle.destroyShaderModule(vert_shader); + device_handle.destroyShaderModule(frag_shader); + + return pipeline; +} + +void text_renderer::begin_frame(uint32_t frame_index, const vec2f_t& viewport_size) { + current_frame_ = frame_index % MAX_FRAMES_IN_FLIGHT; + viewport_size_ = viewport_size; + + // 重置描述符池 + auto device_handle = device_.get_handle(); + (void)device_handle.resetDescriptorPool(descriptor_pools_[current_frame_]); + + // 重置资源使用计数 + frame_data_[current_frame_].used_vertex_count = 0; + frame_data_[current_frame_].used_index_count = 0; + + // 如果图集脏了,更新它 + if (atlas_dirty_) { + update_atlas_texture(); + } +} + +void text_renderer::render_impl(vk::CommandBuffer cmd, const text_batch& batch) { + std::cout << "[TEXT_RENDERER] render_impl called, vertices=" << batch.vertices.size() + << ", indices=" << batch.indices.size() << std::endl; + + if (batch.vertices.empty() || batch.indices.empty()) { + std::cout << "[TEXT_RENDERER] Empty batch, skipping render" << std::endl; + return; + } + + // 检查 atlas_view_ 是否有效 + if (!atlas_view_) { + std::cerr << "[TEXT_RENDERER] ERROR: atlas_view_ is null!" << std::endl; + return; + } + + std::cout << "[TEXT_RENDERER] atlas_view_ is valid, proceeding with render" << std::endl; + + // ========== 调试:打印前几个顶点的数据 ========== + std::cout << "[TEXT_RENDERER] === Vertex Data Debug ===" << std::endl; + size_t debug_count = std::min(batch.vertices.size(), size_t(8)); + for (size_t i = 0; i < debug_count; ++i) { + const auto& v = batch.vertices[i]; + std::cout << "[TEXT_RENDERER] Vertex[" << i << "]: " + << "pos=(" << v.position[0] << ", " << v.position[1] << "), " + << "uv=(" << v.uv[0] << ", " << v.uv[1] << "), " + << "color=(" << v.color[0] << ", " << v.color[1] << ", " + << v.color[2] << ", " << v.color[3] << ")" << std::endl; + } + + // 打印视口大小 + std::cout << "[TEXT_RENDERER] Viewport size: " << viewport_size_.x() << " x " << viewport_size_.y() << std::endl; + + auto& frame_res = frame_data_[current_frame_]; + + // 检查缓冲区空间 + if (frame_res.used_vertex_count + batch.vertices.size() > frame_res.vertices.size()) { + std::cerr << "[TEXT_RENDERER] Vertex buffer overflow" << std::endl; + return; + } + if (frame_res.used_index_count + batch.indices.size() > frame_res.indices.size()) { + std::cerr << "[TEXT_RENDERER] Index buffer overflow" << std::endl; + return; + } + + // 选择管线 + vk::Pipeline pipeline = standard_pipeline_; + if (batch.shader_id != STANDARD_SHADER_ID) { + auto it = custom_pipelines_.find(batch.shader_id); + if (it != custom_pipelines_.end()) { + pipeline = it->second; + } + } + + // 绑定管线 + cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); + + // 设置视口和裁剪 + vk::Viewport viewport{ + 0.0f, 0.0f, + viewport_size_.x(), viewport_size_.y(), + 0.0f, 1.0f + }; + cmd.setViewport(0, 1, &viewport); + + vk::Rect2D scissor{ + {0, 0}, + {static_cast(viewport_size_.x()), static_cast(viewport_size_.y())} + }; + cmd.setScissor(0, 1, &scissor); + + // 分配并更新描述符集 + vk::DescriptorSet atlas_set = allocate_descriptor_set(current_frame_); + update_descriptor_set(atlas_set, atlas_view_, atlas_sampler_); + + // 绑定描述符集 + cmd.bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, + pipeline_layout_, + 0, 1, &atlas_set, + 0, nullptr + ); + + // 更新Push Constants + auto constants = create_push_constants(viewport_size_.x(), viewport_size_.y(), 0.0f); + cmd.pushConstants( + pipeline_layout_, + vk::ShaderStageFlagBits::eVertex, + 0, + sizeof(push_constants), + &constants + ); + + // 上传顶点和索引数据 + frame_res.vertices.upload(batch.vertices, frame_res.used_vertex_count); + frame_res.indices.upload(batch.indices, frame_res.used_index_count); + + // 绑定顶点缓冲 + vk::Buffer vertex_buffers[] = {frame_res.vertices.get_buffer().buffer}; + vk::DeviceSize offsets[] = {0}; + cmd.bindVertexBuffers(0, 1, vertex_buffers, offsets); + + // 绑定索引缓冲 + cmd.bindIndexBuffer( + frame_res.indices.get_buffer().buffer, + 0, + vk::IndexType::eUint32 + ); + + // 执行绘制 + cmd.drawIndexed( + static_cast(batch.indices.size()), + 1, + frame_res.used_index_count, + static_cast(frame_res.used_vertex_count), + 0 + ); + + // 更新使用计数 + frame_res.used_vertex_count += static_cast(batch.vertices.size()); + frame_res.used_index_count += static_cast(batch.indices.size()); +} + +auto text_renderer::allocate_descriptor_set(uint32_t frame_index) -> vk::DescriptorSet { + vk::DescriptorSetAllocateInfo alloc_info{}; + alloc_info.descriptorPool = descriptor_pools_[frame_index]; + alloc_info.descriptorSetCount = 1; + alloc_info.pSetLayouts = &atlas_set_layout_; + + auto [result, sets] = device_.get_handle().allocateDescriptorSets(alloc_info); + if (result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to allocate descriptor set for text atlas"); + } + + return sets[0]; +} + +void text_renderer::update_descriptor_set( + vk::DescriptorSet set, + vk::ImageView atlas_view, + vk::Sampler sampler +) { + vk::DescriptorImageInfo image_info{}; + image_info.sampler = sampler; + image_info.imageView = atlas_view; + image_info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + + vk::WriteDescriptorSet write{}; + write.dstSet = set; + write.dstBinding = 0; + write.dstArrayElement = 0; + write.descriptorType = vk::DescriptorType::eCombinedImageSampler; + write.descriptorCount = 1; + write.pImageInfo = &image_info; + + device_.get_handle().updateDescriptorSets(1, &write, 0, nullptr); +} + +void text_renderer::transition_image_layout( + vk::Image image, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout +) { + auto device_handle = device_.get_handle(); + + // 创建临时命令缓冲区 + vk::CommandBufferAllocateInfo alloc_info{}; + alloc_info.commandPool = device_.get_command_pool(); + alloc_info.level = vk::CommandBufferLevel::ePrimary; + alloc_info.commandBufferCount = 1; + + auto [alloc_result, cmd_buffers] = device_handle.allocateCommandBuffers(alloc_info); + if (alloc_result != vk::Result::eSuccess || cmd_buffers.empty()) { + std::cerr << "[TEXT_RENDERER] Failed to allocate command buffer for layout transition" << std::endl; + return; + } + + vk::CommandBuffer cmd = cmd_buffers[0]; + + // 开始记录命令 + vk::CommandBufferBeginInfo begin_info{}; + begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; + + if (cmd.begin(&begin_info) != vk::Result::eSuccess) { + std::cerr << "[TEXT_RENDERER] Failed to begin command buffer" << std::endl; + device_handle.freeCommandBuffers(device_.get_command_pool(), cmd); + return; + } + + // 配置图像内存屏障 + vk::ImageMemoryBarrier barrier{}; + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + // 根据布局转换设置访问掩码和管线阶段 + vk::PipelineStageFlags src_stage; + vk::PipelineStageFlags dst_stage; + + if (old_layout == vk::ImageLayout::eUndefined && + new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) { + // 从未定义布局转换到着色器只读布局 + barrier.srcAccessMask = vk::AccessFlags{}; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + src_stage = vk::PipelineStageFlagBits::eTopOfPipe; + dst_stage = vk::PipelineStageFlagBits::eFragmentShader; + } else if (old_layout == vk::ImageLayout::eUndefined && + new_layout == vk::ImageLayout::eTransferDstOptimal) { + // 从未定义布局转换到传输目标布局 + barrier.srcAccessMask = vk::AccessFlags{}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + src_stage = vk::PipelineStageFlagBits::eTopOfPipe; + dst_stage = vk::PipelineStageFlagBits::eTransfer; + } else if (old_layout == vk::ImageLayout::eTransferDstOptimal && + new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) { + // 从传输目标布局转换到着色器只读布局 + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + src_stage = vk::PipelineStageFlagBits::eTransfer; + dst_stage = vk::PipelineStageFlagBits::eFragmentShader; + } else { + std::cerr << "[TEXT_RENDERER] Unsupported layout transition" << std::endl; + cmd.end(); + device_handle.freeCommandBuffers(device_.get_command_pool(), cmd); + return; + } + + // 执行管线屏障 + cmd.pipelineBarrier( + src_stage, dst_stage, + vk::DependencyFlags{}, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + // 结束命令缓冲区 + if (cmd.end() != vk::Result::eSuccess) { + std::cerr << "[TEXT_RENDERER] Failed to end command buffer" << std::endl; + device_handle.freeCommandBuffers(device_.get_command_pool(), cmd); + return; + } + + // 提交命令缓冲区 + vk::SubmitInfo submit_info{}; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &cmd; + + auto queue = device_.get_graphics_queue(); + if (queue.submit(1, &submit_info, nullptr) != vk::Result::eSuccess) { + std::cerr << "[TEXT_RENDERER] Failed to submit layout transition command" << std::endl; + } + + // 等待完成 + queue.waitIdle(); + + // 释放命令缓冲区 + device_handle.freeCommandBuffers(device_.get_command_pool(), cmd); +} + +void text_renderer::update_atlas_texture() { + std::cout << "[TEXT_RENDERER] update_atlas_texture called" << std::endl; + + auto& atlas = glyph_cache_.get_atlas(); + + if (!atlas.is_dirty()) { + std::cout << "[TEXT_RENDERER] Atlas is not dirty, skipping update" << std::endl; + atlas_dirty_ = false; + return; + } + + std::cout << "[TEXT_RENDERER] Atlas is dirty, updating texture" << std::endl; + + const auto& atlas_data = atlas.get_atlas_data(); + const uint32_t atlas_size = atlas.get_atlas_size(); + const vk::DeviceSize image_size = atlas_data.size(); + + std::cout << "[TEXT_RENDERER] Atlas size: " << atlas_size << "x" << atlas_size + << ", data size: " << image_size << " bytes" << std::endl; + + // ========== 调试:检查图集数据是否有非零值 ========== + size_t non_zero_count = 0; + size_t sample_count = std::min(image_size, vk::DeviceSize(1024 * 4)); // 检查前1024个像素 + for (size_t i = 0; i < sample_count; ++i) { + if (atlas_data[i] != 0) { + ++non_zero_count; + } + } + std::cout << "[TEXT_RENDERER] Atlas data check: " << non_zero_count << "/" << sample_count + << " non-zero bytes in first " << (sample_count / 4) << " pixels" << std::endl; + + // 打印前几个像素的RGBA值 + std::cout << "[TEXT_RENDERER] First 4 pixels (RGBA): "; + for (size_t i = 0; i < 16 && i < image_size; i += 4) { + std::cout << "(" << (int)atlas_data[i] << "," << (int)atlas_data[i+1] + << "," << (int)atlas_data[i+2] << "," << (int)atlas_data[i+3] << ") "; + } + std::cout << std::endl; + + // 查找第一个非零像素 + for (size_t i = 0; i < image_size; i += 4) { + if (atlas_data[i] != 0 || atlas_data[i+1] != 0 || atlas_data[i+2] != 0 || atlas_data[i+3] != 0) { + size_t pixel_idx = i / 4; + size_t px = pixel_idx % atlas_size; + size_t py = pixel_idx / atlas_size; + std::cout << "[TEXT_RENDERER] First non-zero pixel at (" << px << "," << py << "): " + << "RGBA=(" << (int)atlas_data[i] << "," << (int)atlas_data[i+1] + << "," << (int)atlas_data[i+2] << "," << (int)atlas_data[i+3] << ")" << std::endl; + break; + } + } + + // 如果atlas_image_还没创建或尺寸不匹配,创建新的 + bool need_create_image = !atlas_image_.image || atlas_image_.format != vk::Format::eR8G8B8A8Unorm; + + if (need_create_image) { + // 销毁旧的资源 + if (atlas_image_.image) { + // 注意:atlas_view_ 和 atlas_image_.view 是同一个对象 + atlas_view_ = nullptr; + res_mgr_.destroy_image(atlas_image_); + atlas_image_ = {}; + } + + // 创建新纹理 + auto image_result = res_mgr_.create_image( + atlas_size, atlas_size, + vk::Format::eR8G8B8A8Unorm, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal + ); + + if (!image_result) { + std::cerr << "[TEXT_RENDERER] Failed to create atlas image" << std::endl; + return; + } + + atlas_image_ = std::move(image_result.value()); + atlas_view_ = atlas_image_.view; + + std::cout << "[TEXT_RENDERER] Created atlas image, view=" + << static_cast(static_cast(atlas_view_)) << std::endl; + } + + // 创建 staging buffer 用于上传纹理数据 + auto staging_result = res_mgr_.create_buffer( + image_size, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent + ); + + if (!staging_result) { + std::cerr << "[TEXT_RENDERER] Failed to create staging buffer" << std::endl; + return; + } + + auto staging_buffer = std::move(staging_result.value()); + + // 复制数据到 staging buffer + if (staging_buffer.mapped_data) { + std::memcpy(staging_buffer.mapped_data, atlas_data.data(), image_size); + std::cout << "[TEXT_RENDERER] Copied " << image_size << " bytes to staging buffer" << std::endl; + } else { + std::cerr << "[TEXT_RENDERER] Staging buffer not mapped!" << std::endl; + res_mgr_.destroy_buffer(staging_buffer); + return; + } + + // 创建命令缓冲区执行传输 + auto device_handle = device_.get_handle(); + + vk::CommandBufferAllocateInfo alloc_info{}; + alloc_info.commandPool = device_.get_command_pool(); + alloc_info.level = vk::CommandBufferLevel::ePrimary; + alloc_info.commandBufferCount = 1; + + auto [alloc_result, cmd_buffers] = device_handle.allocateCommandBuffers(alloc_info); + if (alloc_result != vk::Result::eSuccess || cmd_buffers.empty()) { + std::cerr << "[TEXT_RENDERER] Failed to allocate command buffer for texture upload" << std::endl; + res_mgr_.destroy_buffer(staging_buffer); + return; + } + + vk::CommandBuffer cmd = cmd_buffers[0]; + + // 开始记录命令 + vk::CommandBufferBeginInfo begin_info{}; + begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; + + if (cmd.begin(&begin_info) != vk::Result::eSuccess) { + std::cerr << "[TEXT_RENDERER] Failed to begin command buffer" << std::endl; + device_handle.freeCommandBuffers(device_.get_command_pool(), cmd); + res_mgr_.destroy_buffer(staging_buffer); + return; + } + + // 1. 转换图像布局:UNDEFINED/SHADER_READ_ONLY -> TRANSFER_DST + { + vk::ImageMemoryBarrier barrier{}; + barrier.oldLayout = need_create_image ? vk::ImageLayout::eUndefined : vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.newLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = atlas_image_.image; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcAccessMask = need_create_image ? vk::AccessFlags{} : vk::AccessFlagBits::eShaderRead; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + cmd.pipelineBarrier( + need_create_image ? vk::PipelineStageFlagBits::eTopOfPipe : vk::PipelineStageFlagBits::eFragmentShader, + vk::PipelineStageFlagBits::eTransfer, + vk::DependencyFlags{}, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + } + + // 2. 复制 buffer 到 image + vk::BufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; // 紧密打包 + region.bufferImageHeight = 0; // 紧密打包 + region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = vk::Offset3D{0, 0, 0}; + region.imageExtent = vk::Extent3D{atlas_size, atlas_size, 1}; + + cmd.copyBufferToImage( + staging_buffer.buffer, + atlas_image_.image, + vk::ImageLayout::eTransferDstOptimal, + 1, ®ion + ); + + // 3. 转换图像布局:TRANSFER_DST -> SHADER_READ_ONLY + { + vk::ImageMemoryBarrier barrier{}; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = atlas_image_.image; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + cmd.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, + vk::DependencyFlags{}, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + } + + // 结束命令缓冲区 + if (cmd.end() != vk::Result::eSuccess) { + std::cerr << "[TEXT_RENDERER] Failed to end command buffer" << std::endl; + device_handle.freeCommandBuffers(device_.get_command_pool(), cmd); + res_mgr_.destroy_buffer(staging_buffer); + return; + } + + // 提交命令缓冲区 + vk::SubmitInfo submit_info{}; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &cmd; + + auto queue = device_.get_graphics_queue(); + if (queue.submit(1, &submit_info, nullptr) != vk::Result::eSuccess) { + std::cerr << "[TEXT_RENDERER] Failed to submit texture upload command" << std::endl; + } else { + std::cout << "[TEXT_RENDERER] Texture upload command submitted" << std::endl; + } + + // 等待完成 + queue.waitIdle(); + + // 释放命令缓冲区和 staging buffer + device_handle.freeCommandBuffers(device_.get_command_pool(), cmd); + res_mgr_.destroy_buffer(staging_buffer); + + // 清除图集脏标志 + atlas.clear_dirty_flag(); + atlas_dirty_ = false; + + std::cout << "[TEXT_RENDERER] Atlas texture upload completed successfully" << std::endl; +} + +void text_renderer::end_frame(uint32_t frame_index) { + // 预留用于未来扩展 +} + +auto text_renderer::register_custom_shader( + uint32_t shader_id, + std::span frag_spirv +) -> bool { + try { + // 检查是否已存在 + if (custom_pipelines_.find(shader_id) != custom_pipelines_.end()) { + return false; + } + + // 创建新管线(使用当前的render pass) + // 注意:这需要render pass参数,这里简化处理 + // 实际应该保存render pass引用 + + return true; + } catch (const std::exception& e) { + std::cerr << "[TEXT_RENDERER] Failed to register custom shader: " << e.what() << std::endl; + return false; + } +} + +void text_renderer::cleanup() { + std::cout << "[TEXT_RENDERER] cleanup called" << std::endl; + + auto device_handle = device_.get_handle(); + + // 等待设备空闲 + auto result = device_handle.waitIdle(); + if (result != vk::Result::eSuccess) { + std::cerr << "[TEXT_RENDERER] waitIdle failed" << std::endl; + } + + // 清理管线 + if (standard_pipeline_) { + device_handle.destroyPipeline(standard_pipeline_); + standard_pipeline_ = nullptr; + } + + for (auto& [id, pipeline] : custom_pipelines_) { + if (pipeline) { + device_handle.destroyPipeline(pipeline); + } + } + custom_pipelines_.clear(); + + if (pipeline_layout_) { + device_handle.destroyPipelineLayout(pipeline_layout_); + pipeline_layout_ = nullptr; + } + + // 清理描述符 + for (auto& pool : descriptor_pools_) { + if (pool) { + device_handle.destroyDescriptorPool(pool); + } + } + descriptor_pools_.clear(); + + if (atlas_set_layout_) { + device_handle.destroyDescriptorSetLayout(atlas_set_layout_); + atlas_set_layout_ = nullptr; + } + + if (params_set_layout_) { + device_handle.destroyDescriptorSetLayout(params_set_layout_); + params_set_layout_ = nullptr; + } + + // 清理采样器 + if (atlas_sampler_) { + device_handle.destroySampler(atlas_sampler_); + atlas_sampler_ = nullptr; + } + + // 清理图集纹理 + // 注意:atlas_view_ 和 atlas_image_.view 指向同一个 ImageView + // 只需要通过 res_mgr_.destroy_image() 销毁一次即可 + if (atlas_image_.image) { + std::cout << "[TEXT_RENDERER] Destroying atlas image, view=" + << static_cast(static_cast(atlas_view_)) + << ", atlas_image_.view=" + << static_cast(static_cast(atlas_image_.view)) << std::endl; + + // 不要手动销毁 atlas_view_,因为它和 atlas_image_.view 是同一个对象 + // res_mgr_.destroy_image() 会负责销毁 view + atlas_view_ = nullptr; // 只清空指针,不销毁 + res_mgr_.destroy_image(atlas_image_); + atlas_image_ = {}; // 重置结构体 + } + + // 清理帧资源 + for (auto& frame : frame_data_) { + res_mgr_.destroy_buffer(frame.vertices.get_buffer()); + res_mgr_.destroy_buffer(frame.indices.get_buffer()); + res_mgr_.destroy_buffer(frame.params.get_buffer()); + } + frame_data_.clear(); +} + +} // namespace mirage::render \ No newline at end of file diff --git a/src/render/pipeline/text_renderer.h b/src/render/pipeline/text_renderer.h new file mode 100644 index 0000000..9c99258 --- /dev/null +++ b/src/render/pipeline/text_renderer.h @@ -0,0 +1,188 @@ +#pragma once + +#include "vulkan/vulkan_common.h" +#include "vulkan/typed_buffer.h" +#include "vulkan/logical_device.h" +#include "vulkan/resource_manager.h" +#include "renderer/vertex_types.h" +#include "text/glyph_cache.h" +#include "text/font_manager.h" +#include "frame_context.h" +#include "pass_state_tags.h" + +#include +#include +#include +#include + +namespace mirage::render { + +// 前向声明 +namespace text { + class glyph_cache; + class font_manager; +} + +// 文本渲染批次 +struct text_batch { + text::font_manager::font_id_t font_id{0}; + std::vector vertices; + std::vector indices; + uint32_t shader_id{0}; // 0 = 标准着色器 +}; + +// 文本着色器参数(用于自定义着色器) +struct text_shader_params { + alignas(16) float outline_color[4]{0, 0, 0, 1}; + alignas(4) float outline_width{0.1f}; + alignas(4) float softness{0.1f}; + alignas(8) float shadow_offset[2]{0.02f, 0.02f}; + alignas(4) float shadow_softness{0.1f}; + alignas(4) float padding{0}; +}; + +class text_renderer { +public: + static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2; + static constexpr uint32_t STANDARD_SHADER_ID = 0; + static constexpr uint32_t OUTLINE_SHADER_ID = 1; + static constexpr uint32_t SHADOW_SHADER_ID = 2; + + /// 配置参数 + struct config { + uint32_t initial_vertex_capacity = 4096; ///< 初始顶点容量 + uint32_t initial_index_capacity = 6144; ///< 初始索引容量 + }; + + text_renderer( + logical_device& device, + resource_manager& res_mgr, + text::glyph_cache& glyph_cache, + const config& cfg = {} + ); + ~text_renderer(); + + // 禁止拷贝 + text_renderer(const text_renderer&) = delete; + auto operator=(const text_renderer&) -> text_renderer& = delete; + + // 初始化渲染器(创建管线等) + void initialize(vk::RenderPass render_pass); + + // 帧开始 + void begin_frame(uint32_t frame_index, const vec2f_t& viewport_size); + + // ======================================================================== + // 类型安全接口 - 使用 frame_context + // ======================================================================== + + /// @brief 渲染文本批次(类型安全版本) + /// @tparam State 当前帧上下文状态,必须是活动 Pass 状态 + /// @param ctx 帧上下文引用 + /// @param batch 文本批次 + template + void render(pass_state::frame_context& ctx, const text_batch& batch) { + render_impl(ctx.cmd(), batch); + } + + // ======================================================================== + // 低级接口 - 直接操作命令缓冲区 + // ======================================================================== + + /// @brief 渲染文本批次(低级版本) + /// @param cmd 命令缓冲 + /// @param batch 文本批次 + void render_impl(vk::CommandBuffer cmd, const text_batch& batch); + + /// @brief 渲染文本批次(兼容性别名) + /// @deprecated 推荐使用类型安全版本 render(frame_context&, batch) + void render(vk::CommandBuffer cmd, const text_batch& batch) { + render_impl(cmd, batch); + } + + // 帧结束 + void end_frame(uint32_t frame_index); + + // 注册自定义着色器 + auto register_custom_shader( + uint32_t shader_id, + std::span frag_spirv + ) -> bool; + + // 更新图集纹理(当glyph_cache添加新字形时调用) + void update_atlas_texture(); + + // 清理资源 + void cleanup(); + +private: + // 创建管线 + auto create_pipeline( + vk::RenderPass render_pass, + std::span frag_spirv + ) -> vk::Pipeline; + + // 创建描述符集布局 + void create_descriptor_set_layouts(); + + // 创建描述符池 + void create_descriptor_pool(); + + // 创建帧资源 + void create_frame_resources(); + + // 分配描述符集 + auto allocate_descriptor_set(uint32_t frame_index) -> vk::DescriptorSet; + + // 更新描述符集 + void update_descriptor_set( + vk::DescriptorSet set, + vk::ImageView atlas_view, + vk::Sampler sampler + ); + + // 转换图像布局(辅助函数) + void transition_image_layout( + vk::Image image, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout + ); + + logical_device& device_; + resource_manager& res_mgr_; + text::glyph_cache& glyph_cache_; + config config_; + + // 管线相关 + vk::PipelineLayout pipeline_layout_; + vk::Pipeline standard_pipeline_; + std::unordered_map custom_pipelines_; + + // 描述符相关 + vk::DescriptorSetLayout atlas_set_layout_; // set 0: 图集纹理 + vk::DescriptorSetLayout params_set_layout_; // set 1: 自定义参数 + std::vector descriptor_pools_; + + // 图集纹理资源 + resource_manager::image_resource atlas_image_; + vk::ImageView atlas_view_; + vk::Sampler atlas_sampler_; + bool atlas_dirty_{true}; + + // 每帧资源 + struct frame_data { + typed_buffer vertices; + typed_buffer indices; + typed_buffer params; + uint32_t used_vertex_count = 0; + uint32_t used_index_count = 0; + }; + + std::vector frame_data_; + + // 当前帧状态 + uint32_t current_frame_{0}; + vec2f_t viewport_size_{}; +}; + +} // namespace mirage::render \ No newline at end of file diff --git a/src/render/pipeline/types.h b/src/render/pipeline/types.h index 8383662..4340db6 100644 --- a/src/render/pipeline/types.h +++ b/src/render/pipeline/types.h @@ -18,6 +18,8 @@ namespace mirage { std::vector indices; ///< 索引数据 std::optional texture_id; std::optional clip_rect; + bool is_text_batch = false; ///< 是否是文本批次 + uint32_t font_id = 0; ///< 字体ID(仅文本批次有效) /// 检查批次是否为空 [[nodiscard]] auto empty() const -> bool { @@ -30,6 +32,8 @@ namespace mirage { indices.clear(); texture_id.reset(); clip_rect.reset(); + is_text_batch = false; + font_id = 0; } /// 预留容量 diff --git a/src/render/shaders/widget/mtsdf_text.frag.glsl b/src/render/shaders/widget/mtsdf_text.frag.glsl new file mode 100644 index 0000000..a1a03ba --- /dev/null +++ b/src/render/shaders/widget/mtsdf_text.frag.glsl @@ -0,0 +1,125 @@ +#version 450 core + +/** + * @brief MTSDF文本渲染片段着色器(标准版本) + * + * 使用多通道有符号距离场(Multi-channel Signed Distance Field)技术渲染文本。 + * MTSDF在RGB三个通道中存储不同方向的距离场信息,通过median函数计算中值距离, + * 可以在拐角处获得比传统SDF更好的视觉质量。 + * + * MTSDF数据格式: + * - 原始距离场值范围: [-pixel_range, +pixel_range] + * - 归一化后存储范围: [0, 1] + * - 边界值: 0.5 (对应原始距离0) + * - pixel_range: 生成时使用的像素范围(默认4.0) + */ + +// ============================================================================ +// 输入 +// ============================================================================ + +layout(location = 0) in vec4 frag_color; ///< 文本颜色 +layout(location = 1) in vec2 frag_uv; ///< 纹理坐标 +layout(location = 2) in vec2 frag_screen_pos; ///< 屏幕空间位置(预留) + +// ============================================================================ +// 输出 +// ============================================================================ + +layout(location = 0) out vec4 out_color; ///< 最终颜色输出 + +// ============================================================================ +// 纹理绑定 +// ============================================================================ + +/** + * @brief MTSDF图集纹理 + * RGB通道存储多方向的有符号距离场(已归一化到[0,1]) + * Alpha通道存储真实距离场(用于特效) + */ +layout(set = 0, binding = 0) uniform sampler2D u_atlas; + +// ============================================================================ +// 常量定义 +// ============================================================================ + +/** + * @brief MTSDF生成时使用的像素范围 + * + * 这个值必须与 mtsdf_generator.cpp 中的 pixel_range 参数一致。 + * 默认值为4.0,表示距离场覆盖字形边界周围4个像素的范围。 + */ +const float PIXEL_RANGE = 4.0; + +/** + * @brief 字形纹理大小 + * + * 这个值必须与 glyph_cache.h 中的 glyph_size 一致。 + * 默认值为48像素。 + */ +const float GLYPH_TEXTURE_SIZE = 48.0; + +// ============================================================================ +// MTSDF核心算法 +// ============================================================================ + +/** + * @brief 计算RGB三通道的中值 + * + * 这是MTSDF的核心算法。通过计算三个距离场的中值, + * 可以在字形的拐角处获得更准确的距离估计。 + * + * @param r 红色通道距离值 + * @param g 绿色通道距离值 + * @param b 蓝色通道距离值 + * @return 中值距离 + */ +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +/** + * @brief 计算屏幕像素范围 + * + * 根据UV坐标的屏幕空间导数计算当前片段对应的屏幕像素范围。 + * 这用于自适应抗锯齿,确保在不同缩放级别下都能获得清晰的边缘。 + * + * @return 屏幕像素范围(用于距离场到屏幕像素的转换) + */ +float compute_screen_px_range() { + // 计算UV坐标在屏幕空间的变化率 + // fwidth(uv) 返回 |dFdx(uv)| + |dFdy(uv)| + vec2 unit_range = vec2(PIXEL_RANGE) / vec2(GLYPH_TEXTURE_SIZE); + vec2 screen_tex_size = vec2(1.0) / fwidth(frag_uv); + return max(0.5 * dot(unit_range, screen_tex_size), 1.0); +} + +// ============================================================================ +// 主函数 +// ============================================================================ + +void main() { + // ========== 正常渲染逻辑 ========== + // 采样MTSDF纹理(RGB通道存储多通道距离场,已归一化到[0,1]) + vec4 mtsdf = texture(u_atlas, frag_uv); + + // 计算中值距离 + // 归一化后的值范围: [0, 1],其中0.5表示字形边界 + float sd = median(mtsdf.r, mtsdf.g, mtsdf.b); + + // 计算屏幕像素范围 + // 这个值表示距离场中的1个单位对应多少屏幕像素 + float screen_px_range = compute_screen_px_range(); + + // 将归一化的距离值转换为屏幕像素距离 + // sd - 0.5: 将[0,1]范围转换为[-0.5, 0.5],边界在0 + // * screen_px_range: 转换为屏幕像素单位 + float screen_px_distance = screen_px_range * (sd - 0.5); + + // 使用平滑步函数生成抗锯齿的alpha值 + // +0.5偏移用于中心对齐,使边界处alpha=0.5 + float alpha = clamp(screen_px_distance + 0.5, 0.0, 1.0); + + // 输出最终颜色(保留文本颜色的RGB,alpha与距离场混合) + out_color = vec4(frag_color.rgb, frag_color.a * alpha); +} \ No newline at end of file diff --git a/src/render/shaders/widget/mtsdf_text.vert.glsl b/src/render/shaders/widget/mtsdf_text.vert.glsl new file mode 100644 index 0000000..a795fec --- /dev/null +++ b/src/render/shaders/widget/mtsdf_text.vert.glsl @@ -0,0 +1,65 @@ +#version 450 core + +/** + * @brief MTSDF文本渲染顶点着色器 + * + * 负责将屏幕空间坐标转换为 NDC(归一化设备坐标)。 + * 使用正交投影矩阵进行 2D UI 文本渲染。 + */ + +// ============================================================================ +// Uniform 缓冲 +// ============================================================================ + +/** + * @brief 全局 Push Constants 数据 + * 对应 C++ 结构体: push_constants (uniform_types.h) + */ +layout(push_constant) uniform Constants { + mat4 projection; ///< 正交投影矩阵(屏幕空间 -> NDC) + vec2 viewport_size; ///< 视口大小 (width, height) + float time; ///< 时间(秒),用于动画效果 + float padding; ///< 对齐填充 +} constants; + +// ============================================================================ +// 顶点输入属性 +// ============================================================================ + +/** + * @brief 顶点输入 + * 对应 C++ 结构体: ui_vertex (vertex_types.h) + */ +layout(location = 0) in vec2 in_position; ///< 屏幕空间位置 (x, y) +layout(location = 1) in vec4 in_color; ///< 文本颜色 RGBA [0.0, 1.0] +layout(location = 2) in vec2 in_uv; ///< 图集纹理坐标 (u, v) +layout(location = 3) in vec4 in_data_a; ///< 自定义数据A(预留用于特效) +layout(location = 4) in vec4 in_data_b; ///< 自定义数据B(预留用于特效) +layout(location = 5) in vec4 in_data_c; ///< 自定义数据C(预留用于特效) + +// ============================================================================ +// 输出到片段着色器 +// ============================================================================ + +layout(location = 0) out vec4 frag_color; ///< 文本颜色 +layout(location = 1) out vec2 frag_uv; ///< 纹理坐标 +layout(location = 2) out vec2 frag_screen_pos; ///< 屏幕空间位置(用于特效) + +// ============================================================================ +// 主函数 +// ============================================================================ + +void main() { + // 将屏幕空间位置转换为 NDC(归一化设备坐标) + // 正交投影: [0, width] x [0, height] -> [-1, 1] x [-1, 1] + gl_Position = constants.projection * vec4(in_position, 0.0, 1.0); + + // 传递文本颜色到片段着色器 + frag_color = in_color; + + // 传递纹理坐标(范围 [0, 1]) + frag_uv = in_uv; + + // 传递屏幕空间位置(可用于自定义特效) + frag_screen_pos = in_position; +} \ No newline at end of file diff --git a/src/render/shaders/widget/mtsdf_text_outline.frag.glsl b/src/render/shaders/widget/mtsdf_text_outline.frag.glsl new file mode 100644 index 0000000..524c2b4 --- /dev/null +++ b/src/render/shaders/widget/mtsdf_text_outline.frag.glsl @@ -0,0 +1,94 @@ +#version 450 core + +/** + * @brief MTSDF文本渲染片段着色器(描边效果) + * + * 在标准MTSDF渲染的基础上添加描边效果。 + * 通过在不同的距离阈值处采样MTSDF,可以生成外描边。 + * 描边颜色和宽度可通过uniform参数自定义。 + */ + +// ============================================================================ +// 输入 +// ============================================================================ + +layout(location = 0) in vec4 frag_color; ///< 文本颜色 +layout(location = 1) in vec2 frag_uv; ///< 纹理坐标 +layout(location = 2) in vec2 frag_screen_pos; ///< 屏幕空间位置(预留) + +// ============================================================================ +// 输出 +// ============================================================================ + +layout(location = 0) out vec4 out_color; ///< 最终颜色输出 + +// ============================================================================ +// 纹理绑定 +// ============================================================================ + +/** + * @brief MTSDF图集纹理 + */ +layout(set = 0, binding = 0) uniform sampler2D u_atlas; + +/** + * @brief 自定义描边参数 + * 通过descriptor set 1传递,允许运行时动态调整 + */ +layout(set = 1, binding = 0) uniform CustomParams { + vec4 outline_color; ///< 描边颜色 RGBA + float outline_width; ///< 描边宽度(距离场空间,范围0.0-0.5) + float softness; ///< 边缘柔和度(用于平滑过渡) + float padding[2]; ///< 对齐填充 +} params; + +// ============================================================================ +// MTSDF核心算法 +// ============================================================================ + +/** + * @brief 计算RGB三通道的中值 + */ +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +// ============================================================================ +// 主函数 +// ============================================================================ + +void main() { + // 采样MTSDF纹理 + vec4 mtsdf = texture(u_atlas, frag_uv); + float sd = median(mtsdf.r, mtsdf.g, mtsdf.b); + + // 计算屏幕空间像素范围(用于抗锯齿) + float screen_px_range = length(fwidth(frag_uv)) * 100.0; + + // 计算内部文字的alpha + // 标准阈值为0.5,距离场值大于0.5的为字形内部 + float inner_distance = (sd - 0.5) * screen_px_range; + float inner_alpha = clamp(inner_distance + 0.5, 0.0, 1.0); + + // 计算描边的alpha + // 通过向外扩展阈值来生成描边区域 + // outline_width为正值时向外扩展,负值时向内收缩 + float outline_distance = (sd - 0.5 + params.outline_width) * screen_px_range; + float outline_alpha = clamp(outline_distance + 0.5, 0.0, 1.0); + + // 混合文字和描边 + // 先绘制描边(外层),再绘制文字(内层) + vec4 text_color = vec4(frag_color.rgb, frag_color.a * inner_alpha); + vec4 border_color = vec4(params.outline_color.rgb, params.outline_color.a * outline_alpha); + + // 使用inner_alpha作为混合权重,确保文字在描边之上 + // inner_alpha为1时完全显示文字,为0时完全显示描边 + out_color = mix(border_color, text_color, inner_alpha); + + // 应用柔和度调整(可选) + // 通过params.softness可以进一步平滑边缘过渡 + if (params.softness > 0.0) { + float soft_factor = smoothstep(0.0, params.softness, inner_alpha); + out_color = mix(border_color, text_color, soft_factor); + } +} \ No newline at end of file diff --git a/src/render/shaders/widget/mtsdf_text_shadow.frag.glsl b/src/render/shaders/widget/mtsdf_text_shadow.frag.glsl new file mode 100644 index 0000000..8f07941 --- /dev/null +++ b/src/render/shaders/widget/mtsdf_text_shadow.frag.glsl @@ -0,0 +1,99 @@ +#version 450 core + +/** + * @brief MTSDF文本渲染片段着色器(阴影效果) + * + * 在标准MTSDF渲染的基础上添加投影阴影效果。 + * 通过在偏移位置采样MTSDF,可以生成柔和的阴影。 + * 阴影颜色、偏移量和柔和度可通过uniform参数自定义。 + */ + +// ============================================================================ +// 输入 +// ============================================================================ + +layout(location = 0) in vec4 frag_color; ///< 文本颜色 +layout(location = 1) in vec2 frag_uv; ///< 纹理坐标 +layout(location = 2) in vec2 frag_screen_pos; ///< 屏幕空间位置(预留) + +// ============================================================================ +// 输出 +// ============================================================================ + +layout(location = 0) out vec4 out_color; ///< 最终颜色输出 + +// ============================================================================ +// 纹理绑定 +// ============================================================================ + +/** + * @brief MTSDF图集纹理 + */ +layout(set = 0, binding = 0) uniform sampler2D u_atlas; + +/** + * @brief 自定义阴影参数 + * 通过descriptor set 1传递,允许运行时动态调整 + */ +layout(set = 1, binding = 0) uniform CustomParams { + vec4 shadow_color; ///< 阴影颜色 RGBA + vec2 shadow_offset; ///< 阴影偏移(纹理坐标空间) + float shadow_softness; ///< 阴影柔和度(用于模糊效果) + float padding; ///< 对齐填充 +} params; + +// ============================================================================ +// MTSDF核心算法 +// ============================================================================ + +/** + * @brief 计算RGB三通道的中值 + */ +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +// ============================================================================ +// 主函数 +// ============================================================================ + +void main() { + // 采样文字位置的MTSDF + vec4 mtsdf = texture(u_atlas, frag_uv); + float sd = median(mtsdf.r, mtsdf.g, mtsdf.b); + + // 采样阴影位置的MTSDF(偏移坐标) + vec4 shadow_mtsdf = texture(u_atlas, frag_uv + params.shadow_offset); + float shadow_sd = median(shadow_mtsdf.r, shadow_mtsdf.g, shadow_mtsdf.b); + + // 计算屏幕空间像素范围(用于抗锯齿) + float screen_px_range = length(fwidth(frag_uv)) * 100.0; + + // 计算文字的alpha + // 标准阈值为0.5 + float text_distance = (sd - 0.5) * screen_px_range; + float text_alpha = clamp(text_distance + 0.5, 0.0, 1.0); + + // 计算阴影的alpha + // 通过shadow_softness调整阈值,可以控制阴影的柔和程度 + // softness越大,阴影越柔和(边缘越模糊) + float shadow_distance = (shadow_sd - 0.5 - params.shadow_softness) * screen_px_range; + float shadow_alpha = clamp(shadow_distance + 0.5, 0.0, 1.0); + + // 阴影只在文字不存在的地方显示 + // 使用(1.0 - text_alpha)确保阴影不会覆盖文字本身 + shadow_alpha *= (1.0 - text_alpha); + + // 混合阴影和文字 + vec4 text_color = vec4(frag_color.rgb, frag_color.a * text_alpha); + vec4 shadow = vec4(params.shadow_color.rgb, params.shadow_color.a * shadow_alpha); + + // 先绘制阴影(底层),再绘制文字(上层) + // text_alpha为混合权重,确保文字完全不透明时看不到阴影 + out_color = mix(shadow, text_color, text_alpha); + + // 如果文字和阴影都完全透明,输出透明像素 + if (text_alpha <= 0.0 && shadow_alpha <= 0.0) { + out_color = vec4(0.0); + } +} \ No newline at end of file diff --git a/src/render/text/font_atlas.cpp b/src/render/text/font_atlas.cpp new file mode 100644 index 0000000..ec27cf0 --- /dev/null +++ b/src/render/text/font_atlas.cpp @@ -0,0 +1,153 @@ +#include "font_atlas.h" + +#include +#include + +namespace mirage::render::text { + +font_atlas::font_atlas(uint32_t atlas_size) + : atlas_size_(atlas_size) + , atlas_data_(atlas_size * atlas_size * 4, 0) // RGBA格式,初始化为0 +{ + // 初始化时整个图集都是空闲的 + free_rects_.push_back({0, 0, atlas_size, atlas_size}); +} + +auto font_atlas::add_glyph( + uint32_t glyph_id, + const mtsdf_generator::glyph_bitmap& bitmap +) -> bool { + // 检查是否已存在 + if (glyph_rects_.find(glyph_id) != glyph_rects_.end()) { + return true; // 已存在,直接返回成功 + } + + // 查找可用空间 + int node_index = find_free_rect(bitmap.width, bitmap.height); + if (node_index < 0) { + return false; // 空间不足 + } + + // 获取分配位置 + const rect_node& node = free_rects_[node_index]; + uint32_t x = node.x; + uint32_t y = node.y; + + // 复制位图数据到图集 + copy_bitmap_to_atlas(x, y, bitmap); + + // 计算UV坐标(归一化到[0,1]) + float inv_size = 1.0f / static_cast(atlas_size_); + glyph_rect rect; + rect.u0 = x * inv_size; + rect.v0 = y * inv_size; + rect.u1 = (x + bitmap.width) * inv_size; + rect.v1 = (y + bitmap.height) * inv_size; + rect.width = static_cast(bitmap.width); + rect.height = static_cast(bitmap.height); + rect.bearing_x = bitmap.bearing_x; + rect.bearing_y = bitmap.bearing_y; + rect.advance_x = bitmap.advance_x; + + glyph_rects_[glyph_id] = rect; + + // 分割矩形节点 + split_rect_node(node_index, bitmap.width, bitmap.height); + + // 标记为脏(需要重新上传纹理) + dirty_ = true; + + return true; +} + +auto font_atlas::get_glyph_rect(uint32_t glyph_id) const -> const glyph_rect* { + auto it = glyph_rects_.find(glyph_id); + if (it == glyph_rects_.end()) { + return nullptr; + } + return &it->second; +} + +auto font_atlas::get_atlas_data() const -> const std::vector& { + return atlas_data_; +} + +auto font_atlas::get_atlas_size() const -> uint32_t { + return atlas_size_; +} + +auto font_atlas::is_dirty() const -> bool { + return dirty_; +} + +void font_atlas::clear_dirty_flag() { + dirty_ = false; +} + +auto font_atlas::find_free_rect(uint32_t width, uint32_t height) -> int { + int best_index = -1; + uint32_t best_area = UINT32_MAX; + + // 使用Best-Fit策略:选择最小的可容纳矩形 + for (size_t i = 0; i < free_rects_.size(); ++i) { + const rect_node& node = free_rects_[i]; + + if (node.width >= width && node.height >= height) { + uint32_t area = node.width * node.height; + if (area < best_area) { + best_area = area; + best_index = static_cast(i); + } + } + } + + return best_index; +} + +void font_atlas::split_rect_node(int node_index, uint32_t used_width, uint32_t used_height) { + rect_node node = free_rects_[node_index]; + + // 移除已使用的节点 + free_rects_.erase(free_rects_.begin() + node_index); + + // 使用Guillotine分割算法 + // 水平分割:右侧剩余空间 + if (node.width > used_width) { + free_rects_.push_back({ + node.x + used_width, + node.y, + node.width - used_width, + used_height + }); + } + + // 垂直分割:下方剩余空间 + if (node.height > used_height) { + free_rects_.push_back({ + node.x, + node.y + used_height, + node.width, + node.height - used_height + }); + } +} + +void font_atlas::copy_bitmap_to_atlas( + uint32_t x, + uint32_t y, + const mtsdf_generator::glyph_bitmap& bitmap +) { + // 逐行复制RGBA数据 + for (uint32_t row = 0; row < bitmap.height; ++row) { + uint32_t src_offset = row * bitmap.width * 4; + uint32_t dst_offset = ((y + row) * atlas_size_ + x) * 4; + + std::memcpy( + atlas_data_.data() + dst_offset, + bitmap.data.data() + src_offset, + bitmap.width * 4 + ); + } +} + +} // namespace mirage::render::text \ No newline at end of file diff --git a/src/render/text/font_atlas.h b/src/render/text/font_atlas.h new file mode 100644 index 0000000..fb22ded --- /dev/null +++ b/src/render/text/font_atlas.h @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include + +#include "mtsdf_generator.h" + +namespace mirage::render::text { + +/** + * @brief 字体图集 - 管理MTSDF纹理图集,支持动态添加字形 + */ +class font_atlas { +public: + /** + * @brief 字形矩形信息(UV坐标和度量) + */ + struct glyph_rect { + float u0, v0, u1, v1; ///< 纹理坐标(归一化) + float width, height; ///< 字形尺寸(像素) + float bearing_x, bearing_y; ///< 字形基线偏移 + float advance_x; ///< 水平前进量 + }; + + /** + * @brief 构造字体图集 + * + * @param atlas_size 图集纹理尺寸(正方形,默认2048x2048) + */ + explicit font_atlas(uint32_t atlas_size = 2048); + + /** + * @brief 添加字形到图集 + * + * @param glyph_id 字形ID(用于后续查询) + * @param bitmap MTSDF位图数据 + * @return 成功返回true,空间不足返回false + */ + auto add_glyph( + uint32_t glyph_id, + const mtsdf_generator::glyph_bitmap& bitmap + ) -> bool; + + /** + * @brief 获取字形UV坐标和度量信息 + * + * @param glyph_id 字形ID + * @return 字形矩形指针,未找到返回nullptr + */ + auto get_glyph_rect(uint32_t glyph_id) const -> const glyph_rect*; + + /** + * @brief 获取图集纹理数据(用于上传到GPU) + * + * @return RGBA格式的纹理数据 + */ + auto get_atlas_data() const -> const std::vector&; + + /** + * @brief 获取图集尺寸 + */ + auto get_atlas_size() const -> uint32_t; + + /** + * @brief 检查图集是否需要重新上传 + */ + auto is_dirty() const -> bool; + + /** + * @brief 清除脏标志(纹理上传后调用) + */ + void clear_dirty_flag(); + +private: + /** + * @brief 矩形节点(用于矩形打包算法) + */ + struct rect_node { + uint32_t x, y; + uint32_t width, height; + }; + + /** + * @brief 查找可用空间 + * + * @param width 所需宽度 + * @param height 所需高度 + * @return 可用节点索引,失败返回-1 + */ + auto find_free_rect(uint32_t width, uint32_t height) -> int; + + /** + * @brief 分割矩形节点 + * + * @param node_index 节点索引 + * @param used_width 已使用宽度 + * @param used_height 已使用高度 + */ + void split_rect_node(int node_index, uint32_t used_width, uint32_t used_height); + + /** + * @brief 复制位图数据到图集 + * + * @param x 目标X坐标 + * @param y 目标Y坐标 + * @param bitmap 源位图 + */ + void copy_bitmap_to_atlas( + uint32_t x, + uint32_t y, + const mtsdf_generator::glyph_bitmap& bitmap + ); + + uint32_t atlas_size_; ///< 图集尺寸 + std::vector atlas_data_; ///< RGBA格式纹理数据 + std::unordered_map glyph_rects_; ///< 字形矩形映射 + std::vector free_rects_; ///< 空闲矩形列表 + bool dirty_{false}; ///< 脏标志 +}; + +} // namespace mirage::render::text \ No newline at end of file diff --git a/src/render/text/font_manager.cpp b/src/render/text/font_manager.cpp new file mode 100644 index 0000000..d164cf4 --- /dev/null +++ b/src/render/text/font_manager.cpp @@ -0,0 +1,128 @@ +#include "font_manager.h" + +#include + +namespace mirage::render::text { + +font_manager::font_manager() { + FT_Error error = FT_Init_FreeType(&ft_library_); + if (error != 0) { + ft_library_ = nullptr; + } +} + +font_manager::~font_manager() { + // 释放所有字体Face + for (auto& [id, face] : fonts_) { + if (face) { + FT_Done_Face(face); + } + } + fonts_.clear(); + + // 释放FreeType库 + if (ft_library_) { + FT_Done_FreeType(ft_library_); + ft_library_ = nullptr; + } +} + +auto font_manager::load_font(const std::filesystem::path& path) -> expected_t { + if (!ft_library_) { + return std::unexpected("FreeType library not initialized"); + } + + if (!std::filesystem::exists(path)) { + std::ostringstream oss; + oss << "Font file not found: " << path.string(); + return std::unexpected(oss.str()); + } + + FT_Face face = nullptr; + FT_Error error = FT_New_Face( + ft_library_, + path.string().c_str(), + 0, // face_index + &face + ); + + if (error != 0) { + std::ostringstream oss; + oss << "Failed to load font '" << path.string() << "': FreeType error " << error; + return std::unexpected(oss.str()); + } + + // 分配新的字体ID + font_id_t font_id = next_id_++; + fonts_[font_id] = face; + + return font_id; +} + +auto font_manager::get_glyph_index(font_id_t font, char32_t codepoint) const -> uint32_t { + FT_Face face = find_font_face(font); + if (!face) { + return 0; + } + + return FT_Get_Char_Index(face, static_cast(codepoint)); +} + +auto font_manager::load_glyph( + font_id_t font, + uint32_t glyph_index, + uint32_t pixel_size +) -> FT_GlyphSlot { + FT_Face face = find_font_face(font); + if (!face) { + return nullptr; + } + + // 设置字体像素大小 + FT_Error error = FT_Set_Pixel_Sizes(face, 0, pixel_size); + if (error != 0) { + return nullptr; + } + + // 加载字形(不渲染,仅加载轮廓) + error = FT_Load_Glyph( + face, + glyph_index, + FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING + ); + + if (error != 0) { + return nullptr; + } + + return face->glyph; +} + +auto font_manager::get_font_metrics(font_id_t font, uint32_t pixel_size) const -> font_metrics { + FT_Face face = find_font_face(font); + if (!face) { + return {0.0f, 0.0f, 0.0f}; + } + + // 设置字体像素大小 + FT_Set_Pixel_Sizes(face, 0, pixel_size); + + // 从FreeType的26.6定点数转换为浮点像素 + constexpr float scale = 1.0f / 64.0f; + + font_metrics metrics; + metrics.ascender = face->size->metrics.ascender * scale; + metrics.descender = face->size->metrics.descender * scale; + metrics.line_height = face->size->metrics.height * scale; + return metrics; +} + +auto font_manager::find_font_face(font_id_t font) const -> FT_Face { + auto it = fonts_.find(font); + if (it == fonts_.end()) { + return nullptr; + } + return it->second; +} + +} // namespace mirage::render::text \ No newline at end of file diff --git a/src/render/text/font_manager.h b/src/render/text/font_manager.h new file mode 100644 index 0000000..b8e4bf6 --- /dev/null +++ b/src/render/text/font_manager.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include + +#include +#include FT_FREETYPE_H + +#include "types.h" + +namespace mirage::render::text { + +using mirage::expected_t; + +/** + * @brief 字体管理器 - 加载和管理TTF/OTF字体,与FreeType集成 + */ +class font_manager { +public: + using font_id_t = uint32_t; + + /** + * @brief 字体度量信息 + */ + struct font_metrics { + float ascender; ///< 基线以上最大高度 + float descender; ///< 基线以下最大深度(通常为负值) + float line_height; ///< 建议行高 + }; + + font_manager(); + ~font_manager(); + + // 禁止拷贝和移动 + font_manager(const font_manager&) = delete; + font_manager& operator=(const font_manager&) = delete; + font_manager(font_manager&&) = delete; + font_manager& operator=(font_manager&&) = delete; + + /** + * @brief 加载字体文件 + * + * @param path 字体文件路径(.ttf或.otf) + * @return 成功返回字体ID,失败返回错误信息 + */ + auto load_font(const std::filesystem::path& path) -> expected_t; + + /** + * @brief 获取字符对应的字形索引 + * + * @param font 字体ID + * @param codepoint Unicode码点 + * @return 字形索引(0表示未找到) + */ + auto get_glyph_index(font_id_t font, char32_t codepoint) const -> uint32_t; + + /** + * @brief 加载字形到FreeType(供MTSDF生成使用) + * + * @param font 字体ID + * @param glyph_index 字形索引 + * @param pixel_size 字体像素大小 + * @return 字形槽指针,失败返回nullptr + */ + auto load_glyph(font_id_t font, uint32_t glyph_index, uint32_t pixel_size) -> FT_GlyphSlot; + + /** + * @brief 获取字体度量信息 + * + * @param font 字体ID + * @param pixel_size 字体像素大小 + * @return 字体度量信息 + */ + auto get_font_metrics(font_id_t font, uint32_t pixel_size) const -> font_metrics; + +private: + /** + * @brief 查找字体Face + * + * @param font 字体ID + * @return 字体Face指针,未找到返回nullptr + */ + auto find_font_face(font_id_t font) const -> FT_Face; + + FT_Library ft_library_{nullptr}; ///< FreeType库实例 + std::unordered_map fonts_; ///< 已加载的字体映射 + font_id_t next_id_{1}; ///< 下一个可用的字体ID +}; + +} // namespace mirage::render::text \ No newline at end of file diff --git a/src/render/text/glyph_cache.cpp b/src/render/text/glyph_cache.cpp new file mode 100644 index 0000000..b4fd7b9 --- /dev/null +++ b/src/render/text/glyph_cache.cpp @@ -0,0 +1,83 @@ +#include "glyph_cache.h" + +namespace mirage::render::text { + +glyph_cache::glyph_cache( + font_manager& font_mgr, + uint32_t atlas_size, + uint32_t glyph_size +) + : font_mgr_(font_mgr) + , atlas_(atlas_size) + , glyph_size_(glyph_size) +{ +} + +auto glyph_cache::get_or_create_glyph( + font_manager::font_id_t font, + char32_t codepoint +) -> const font_atlas::glyph_rect* { + // 获取字形索引 + uint32_t glyph_index = font_mgr_.get_glyph_index(font, codepoint); + if (glyph_index == 0) { + return nullptr; // 字符不存在于字体中 + } + + // 构建缓存键 + cache_key key{font, glyph_index}; + + // 查找缓存 + auto it = cache_.find(key); + if (it != cache_.end()) { + // 缓存命中,直接返回 + return atlas_.get_glyph_rect(it->second); + } + + // 缓存未命中,生成并添加字形 + uint32_t glyph_id = generate_and_add_glyph(font, glyph_index); + if (glyph_id == 0) { + return nullptr; // 生成失败 + } + + // 添加到缓存 + cache_[key] = glyph_id; + + return atlas_.get_glyph_rect(glyph_id); +} + +auto glyph_cache::get_atlas() -> font_atlas& { + return atlas_; +} + +auto glyph_cache::get_atlas() const -> const font_atlas& { + return atlas_; +} + +auto glyph_cache::generate_and_add_glyph( + font_manager::font_id_t font, + uint32_t glyph_index +) -> uint32_t { + // 从字体管理器加载字形 + FT_GlyphSlot glyph_slot = font_mgr_.load_glyph(font, glyph_index, glyph_size_); + if (!glyph_slot) { + return 0; // 加载失败 + } + + // 生成MTSDF位图 + auto bitmap_opt = mtsdf_generator::generate_mtsdf(glyph_slot, glyph_size_); + if (!bitmap_opt.has_value()) { + return 0; // 生成MTSDF失败 + } + + // 分配新的字形ID + uint32_t glyph_id = next_glyph_id_++; + + // 添加到图集 + if (!atlas_.add_glyph(glyph_id, bitmap_opt.value())) { + return 0; // 图集空间不足 + } + + return glyph_id; +} + +} // namespace mirage::render::text \ No newline at end of file diff --git a/src/render/text/glyph_cache.h b/src/render/text/glyph_cache.h new file mode 100644 index 0000000..10ad388 --- /dev/null +++ b/src/render/text/glyph_cache.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include + +#include "font_atlas.h" +#include "font_manager.h" +#include "mtsdf_generator.h" + +namespace mirage::render::text { + +/** + * @brief 字形缓存 - 协调字体管理器、MTSDF生成器和字体图集,实现字形按需生成和缓存 + */ +class glyph_cache { +public: + /** + * @brief 构造字形缓存 + * + * @param font_mgr 字体管理器引用 + * @param atlas_size 图集纹理尺寸(正方形,默认2048x2048) + * @param glyph_size 生成MTSDF的纹理尺寸(默认48像素) + */ + explicit glyph_cache( + font_manager& font_mgr, + uint32_t atlas_size = 2048, + uint32_t glyph_size = 48 + ); + + /** + * @brief 获取或创建字形(按需生成) + * + * @param font 字体ID + * @param codepoint Unicode码点 + * @return 字形矩形指针,失败返回nullptr + */ + auto get_or_create_glyph( + font_manager::font_id_t font, + char32_t codepoint + ) -> const font_atlas::glyph_rect*; + + /** + * @brief 获取图集引用 + */ + auto get_atlas() -> font_atlas&; + + /** + * @brief 获取图集常量引用 + */ + auto get_atlas() const -> const font_atlas&; + +private: + /** + * @brief 缓存键(字体ID + 字形索引) + */ + struct cache_key { + font_manager::font_id_t font; + uint32_t glyph_index; + + auto operator==(const cache_key& other) const -> bool { + return font == other.font && glyph_index == other.glyph_index; + } + }; + + /** + * @brief 缓存键哈希函数 + */ + struct cache_key_hash { + auto operator()(const cache_key& key) const -> size_t { + return (static_cast(key.font) << 32) | key.glyph_index; + } + }; + + /** + * @brief 生成字形MTSDF并添加到图集 + * + * @param font 字体ID + * @param glyph_index 字形索引 + * @return 成功返回字形ID,失败返回0 + */ + auto generate_and_add_glyph( + font_manager::font_id_t font, + uint32_t glyph_index + ) -> uint32_t; + + font_manager& font_mgr_; ///< 字体管理器引用 + font_atlas atlas_; ///< 字体图集 + uint32_t glyph_size_; ///< MTSDF纹理尺寸 + std::unordered_map cache_; ///< 缓存映射(key -> glyph_id) + uint32_t next_glyph_id_{1}; ///< 下一个可用的字形ID +}; + +} // namespace mirage::render::text \ No newline at end of file diff --git a/src/render/text/mtsdf_generator.cpp b/src/render/text/mtsdf_generator.cpp new file mode 100644 index 0000000..5191521 --- /dev/null +++ b/src/render/text/mtsdf_generator.cpp @@ -0,0 +1,296 @@ +#include "mtsdf_generator.h" + +#include +#include +#include +#include + +namespace mirage::render::text { + +auto mtsdf_generator::generate_mtsdf( + FT_GlyphSlot glyph, + uint32_t size, + double pixel_range +) -> std::optional { + std::cout << "[MTSDF_GEN] generate_mtsdf called, size=" << size + << ", pixel_range=" << pixel_range << std::endl; + + if (!glyph || glyph->format != FT_GLYPH_FORMAT_OUTLINE) { + std::cout << "[MTSDF_GEN] Invalid glyph or not outline format" << std::endl; + return std::nullopt; + } + + std::cout << "[MTSDF_GEN] Glyph outline: n_contours=" << glyph->outline.n_contours + << ", n_points=" << glyph->outline.n_points << std::endl; + + // 创建msdfgen Shape对象 + msdfgen::Shape shape; + if (!build_shape_from_outline(glyph->outline, &shape)) { + std::cout << "[MTSDF_GEN] Failed to build shape from outline" << std::endl; + return std::nullopt; + } + + std::cout << "[MTSDF_GEN] Shape built, contours=" << shape.contours.size() << std::endl; + + // 规范化形状(处理方向和自相交) + shape.normalize(); + + // 计算边缘着色(用于多通道SDF) + msdfgen::edgeColoringSimple(shape, 3.0); + + // 计算边界框 + msdfgen::Shape::Bounds bounds = shape.getBounds(); + + std::cout << "[MTSDF_GEN] Shape bounds: l=" << bounds.l << ", b=" << bounds.b + << ", r=" << bounds.r << ", t=" << bounds.t << std::endl; + + // 处理空字形 + if (bounds.l >= bounds.r || bounds.b >= bounds.t) { + std::cout << "[MTSDF_GEN] Empty glyph (space or similar)" << std::endl; + return glyph_bitmap{ + std::vector(size * size * 4, 0), + size, size, + static_cast(glyph->advance.x / 64.0), + 0.0f, 0.0f + }; + } + + // 创建位图对象(4通道) + msdfgen::Bitmap mtsdf(size, size); + + // 计算投影变换(将字形坐标映射到纹理空间) + double width = bounds.r - bounds.l; + double height = bounds.t - bounds.b; + double scale = std::min( + (size - 2 * pixel_range) / width, + (size - 2 * pixel_range) / height + ); + + msdfgen::Vector2 translate( + -bounds.l + (size / scale - width) * 0.5, + -bounds.b + (size / scale - height) * 0.5 + ); + + msdfgen::Projection projection( + msdfgen::Vector2(scale, scale), + translate + ); + + std::cout << "[MTSDF_GEN] Projection scale=" << scale + << ", translate=(" << translate.x << "," << translate.y << ")" << std::endl; + + // 生成MTSDF + msdfgen::generateMTSDF( + mtsdf, + shape, + projection, + msdfgen::Range(pixel_range) + ); + + // 转换为RGBA8格式 + // 将位图数据复制到连续数组(4通道) + std::vector float_data(size * size * 4); + float min_val = 1e9f, max_val = -1e9f; + for (uint32_t y = 0; y < size; ++y) { + for (uint32_t x = 0; x < size; ++x) { + uint32_t idx = (y * size + x) * 4; + float* pixel = mtsdf(x, y); + float_data[idx + 0] = pixel[0]; + float_data[idx + 1] = pixel[1]; + float_data[idx + 2] = pixel[2]; + float_data[idx + 3] = pixel[3]; + + // 跟踪值范围 + for (int c = 0; c < 3; ++c) { + min_val = std::min(min_val, pixel[c]); + max_val = std::max(max_val, pixel[c]); + } + } + } + + std::cout << "[MTSDF_GEN] MTSDF value range: min=" << min_val << ", max=" << max_val << std::endl; + + // 打印中心像素的值(应该在字形内部) + uint32_t center_x = size / 2; + uint32_t center_y = size / 2; + float* center_pixel = mtsdf(center_x, center_y); + std::cout << "[MTSDF_GEN] Center pixel (" << center_x << "," << center_y << "): " + << "RGBA=(" << center_pixel[0] << "," << center_pixel[1] + << "," << center_pixel[2] << "," << center_pixel[3] << ")" << std::endl; + + auto rgba_data = convert_to_rgba8( + float_data.data(), + size, + size, + pixel_range // 传递pixel_range以便正确归一化 + ); + + // 检查转换后的数据 + size_t non_zero = 0; + for (size_t i = 0; i < rgba_data.size(); ++i) { + if (rgba_data[i] != 0 && rgba_data[i] != 128) non_zero++; + } + std::cout << "[MTSDF_GEN] Converted RGBA data: " << non_zero << "/" << rgba_data.size() + << " non-trivial bytes" << std::endl; + + // 验证归一化后的中心像素值 + uint32_t center_idx = (center_y * size + center_x) * 4; + std::cout << "[MTSDF_GEN] Center pixel AFTER normalization (uint8): " + << "RGBA=(" << (int)rgba_data[center_idx + 0] << "," + << (int)rgba_data[center_idx + 1] << "," + << (int)rgba_data[center_idx + 2] << "," + << (int)rgba_data[center_idx + 3] << ")" << std::endl; + std::cout << "[MTSDF_GEN] Expected: values around 128 at boundary, >128 inside glyph, <128 outside" << std::endl; + + // 构建返回结果 + return glyph_bitmap{ + std::move(rgba_data), + size, + size, + static_cast(glyph->advance.x / 64.0), + static_cast(glyph->metrics.horiBearingX / 64.0), + static_cast(glyph->metrics.horiBearingY / 64.0) + }; +} + +auto mtsdf_generator::build_shape_from_outline( + const FT_Outline& outline, + void* shape_ptr +) -> bool { + auto* shape = static_cast(shape_ptr); + msdfgen::Contour* contour = nullptr; + + msdfgen::Point2 current_point(0, 0); + + for (int i = 0; i < outline.n_points; ) { + char tag = outline.tags[i]; + FT_Vector point = outline.points[i]; + + // 转换FreeType坐标(26.6定点数)到浮点数 + auto to_point = [](const FT_Vector& v) -> msdfgen::Point2 { + return msdfgen::Point2(v.x / 64.0, v.y / 64.0); + }; + + // 检查是否是新轮廓的起点 + bool is_contour_start = (i == 0); + for (int c = 0; c < outline.n_contours; ++c) { + if (outline.contours[c] == i - 1) { + is_contour_start = true; + break; + } + } + + if (is_contour_start) { + shape->contours.push_back(msdfgen::Contour()); + contour = &shape->contours.back(); + current_point = to_point(point); + } + + // 处理不同类型的控制点 + if (tag & FT_CURVE_TAG_ON) { + // 直线段 + msdfgen::Point2 p = to_point(point); + if (contour && !contour->edges.empty()) { + contour->addEdge(msdfgen::EdgeHolder( + current_point, p + )); + } + current_point = p; + ++i; + } else if (tag & FT_CURVE_TAG_CUBIC) { + // 三次贝塞尔曲线 + if (i + 2 < outline.n_points) { + msdfgen::Point2 p1 = to_point(outline.points[i]); + msdfgen::Point2 p2 = to_point(outline.points[i + 1]); + msdfgen::Point2 p3 = to_point(outline.points[i + 2]); + + if (contour) { + contour->addEdge(msdfgen::EdgeHolder( + current_point, p1, p2, p3 + )); + } + current_point = p3; + i += 3; + } else { + ++i; + } + } else { + // 二次贝塞尔曲线 + if (i + 1 < outline.n_points) { + msdfgen::Point2 p1 = to_point(outline.points[i]); + msdfgen::Point2 p2; + + // 检查下一个点 + if (outline.tags[i + 1] & FT_CURVE_TAG_ON) { + p2 = to_point(outline.points[i + 1]); + i += 2; + } else { + // 隐式终点(两个离曲线点之间的中点) + msdfgen::Point2 next_ctrl = to_point(outline.points[i + 1]); + p2 = msdfgen::Point2( + (p1.x + next_ctrl.x) * 0.5, + (p1.y + next_ctrl.y) * 0.5 + ); + ++i; + } + + if (contour) { + contour->addEdge(msdfgen::EdgeHolder( + current_point, p1, p2 + )); + } + current_point = p2; + } else { + ++i; + } + } + } + + return !shape->contours.empty(); +} + +auto mtsdf_generator::convert_to_rgba8( + const float* float_bitmap, + uint32_t width, + uint32_t height, + double pixel_range +) -> std::vector { + std::vector rgba_data(width * height * 4); + + // 归一化函数:将[-pixel_range, +pixel_range]映射到[0, 1] + // 其中距离值0(字形边界)映射到0.5 + auto normalize_and_clamp = [pixel_range](float v) -> uint8_t { + // 将[-pixel_range, +pixel_range]映射到[0, 1] + // v = -pixel_range → 0.0 (字形外部远处) + // v = 0 → 0.5 (字形边界) + // v = +pixel_range → 1.0 (字形内部远处) + float normalized = (v / (2.0f * static_cast(pixel_range))) + 0.5f; + + // 钳位到[0, 1]并转换为字节 + normalized = std::max(0.0f, std::min(1.0f, normalized)); + return static_cast(normalized * 255.0f); + }; + + // 翻转Y轴以修复msdfgen坐标系与Vulkan纹理坐标系的差异 + // msdfgen使用数学坐标系(Y轴向上,原点在左下),Vulkan纹理坐标系Y轴向下(原点在左上) + // 注意:X轴不需要翻转,否则会导致文字左右反转 + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + // 目标索引(正常顺序) + uint32_t dst_idx = (y * width + x) * 4; + // 源索引(仅翻转Y轴,保持X轴不变) + uint32_t src_x = x; // X轴保持不变 + uint32_t src_y = height - 1 - y; // 仅翻转Y轴 + uint32_t src_idx = (src_y * width + src_x) * 4; + + rgba_data[dst_idx + 0] = normalize_and_clamp(float_bitmap[src_idx + 0]); + rgba_data[dst_idx + 1] = normalize_and_clamp(float_bitmap[src_idx + 1]); + rgba_data[dst_idx + 2] = normalize_and_clamp(float_bitmap[src_idx + 2]); + rgba_data[dst_idx + 3] = normalize_and_clamp(float_bitmap[src_idx + 3]); + } + } + + return rgba_data; +} + +} // namespace mirage::render::text \ No newline at end of file diff --git a/src/render/text/mtsdf_generator.h b/src/render/text/mtsdf_generator.h new file mode 100644 index 0000000..737dc75 --- /dev/null +++ b/src/render/text/mtsdf_generator.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +#include +#include FT_FREETYPE_H + +namespace mirage::render::text { + +/** + * @brief MTSDF生成器 - 封装msdfgen库,将FreeType字形轮廓转换为MTSDF纹理 + */ +class mtsdf_generator { +public: + /** + * @brief MTSDF字形位图数据 + */ + struct glyph_bitmap { + std::vector data; ///< RGBA格式纹理数据 + uint32_t width; ///< 位图宽度(像素) + uint32_t height; ///< 位图高度(像素) + float advance_x; ///< 水平前进距离 + float bearing_x; ///< 水平偏移 + float bearing_y; ///< 垂直偏移 + }; + + /** + * @brief 从FreeType字形生成MTSDF位图 + * + * @param glyph FreeType字形槽 + * @param size 输出纹理尺寸(像素) + * @param pixel_range SDF像素范围,控制距离场的扩展范围 + * @return 成功返回字形位图,失败返回nullopt + */ + static auto generate_mtsdf( + FT_GlyphSlot glyph, + uint32_t size, + double pixel_range = 4.0 + ) -> std::optional; + +private: + /** + * @brief 从FreeType轮廓构建msdfgen Shape + * + * @param outline FreeType字形轮廓 + * @return 成功返回true + */ + static auto build_shape_from_outline( + const FT_Outline& outline, + void* shape_ptr + ) -> bool; + + /** + * @brief 将float位图转换为RGBA8格式 + * + * @param float_bitmap 浮点位图数据 + * @param width 位图宽度 + * @param height 位图高度 + * @param pixel_range SDF像素范围,用于正确归一化距离值 + * @return RGBA8格式的字节数据 + */ + static auto convert_to_rgba8( + const float* float_bitmap, + uint32_t width, + uint32_t height, + double pixel_range + ) -> std::vector; +}; + +} // namespace mirage::render::text \ No newline at end of file diff --git a/src/render/text/text_shaper.cpp b/src/render/text/text_shaper.cpp new file mode 100644 index 0000000..cc81274 --- /dev/null +++ b/src/render/text/text_shaper.cpp @@ -0,0 +1,264 @@ +#include "text_shaper.h" + +#include +#include + +namespace mirage::render::text { + +text_shaper::text_shaper(font_manager& font_mgr, glyph_cache& cache) + : font_mgr_(font_mgr) + , cache_(cache) +{ +} + +auto text_shaper::shape_text( + std::u32string_view text, + font_manager::font_id_t font_id, + float font_size, + const vec2f_t& position, + const mirage::color& text_color +) -> shaped_text { + shaped_text result; + + if (text.empty()) { + result.total_width = 0.0f; + result.total_height = 0.0f; + return result; + } + + // 字体缩放因子(这里假设基准大小为48,与glyph_cache的默认glyph_size一致) + const float base_size = 48.0f; + const float scale = font_size / base_size; + + // 第一步:遍历所有字形,找到最大的bearing_y(最高点偏移) + float max_bearing_y = 0.0f; + for (char32_t codepoint : text) { + auto glyph_ptr = cache_.get_or_create_glyph(font_id, codepoint); + if (glyph_ptr) { + max_bearing_y = std::max(max_bearing_y, glyph_ptr->bearing_y * scale); + } + } + + // 调整基线位置,确保字形顶部不会超出屏幕上边界 + // 如果position.y()很小,我们需要向下偏移以确保文本可见 + float baseline_y = position.y() + max_bearing_y; + + // 当前光标位置 + float cursor_x = position.x(); + float cursor_y = baseline_y; + + // 跟踪文本边界 + float min_y = cursor_y; + float max_y = cursor_y; + + // 预分配顶点和索引缓冲 + result.vertices.reserve(text.size() * 4); + result.indices.reserve(text.size() * 6); + + uint32_t base_index = 0; + + for (char32_t codepoint : text) { + // 获取字形信息 + auto glyph_ptr = cache_.get_or_create_glyph(font_id, codepoint); + if (!glyph_ptr) { + // 如果字形不存在,跳过 + continue; + } + + const auto& glyph = *glyph_ptr; + + // 计算字形的屏幕空间位置和尺寸 + const float glyph_x = cursor_x + glyph.bearing_x * scale; + const float glyph_y = cursor_y - glyph.bearing_y * scale; + const float glyph_w = glyph.width * scale; + const float glyph_h = glyph.height * scale; + + // 更新边界 + min_y = std::min(min_y, glyph_y); + max_y = std::max(max_y, glyph_y + glyph_h); + + // 生成四个顶点(左上、右上、右下、左下) + mirage::ui_vertex v0, v1, v2, v3; + + // 左上 + v0.position[0] = glyph_x; + v0.position[1] = glyph_y; + v0.uv[0] = glyph.u0; + v0.uv[1] = glyph.v0; + v0.color[0] = text_color.r; + v0.color[1] = text_color.g; + v0.color[2] = text_color.b; + v0.color[3] = text_color.a; + + // 右上 + v1.position[0] = glyph_x + glyph_w; + v1.position[1] = glyph_y; + v1.uv[0] = glyph.u1; + v1.uv[1] = glyph.v0; + v1.color[0] = text_color.r; + v1.color[1] = text_color.g; + v1.color[2] = text_color.b; + v1.color[3] = text_color.a; + + // 右下 + v2.position[0] = glyph_x + glyph_w; + v2.position[1] = glyph_y + glyph_h; + v2.uv[0] = glyph.u1; + v2.uv[1] = glyph.v1; + v2.color[0] = text_color.r; + v2.color[1] = text_color.g; + v2.color[2] = text_color.b; + v2.color[3] = text_color.a; + + // 左下 + v3.position[0] = glyph_x; + v3.position[1] = glyph_y + glyph_h; + v3.uv[0] = glyph.u0; + v3.uv[1] = glyph.v1; + v3.color[0] = text_color.r; + v3.color[1] = text_color.g; + v3.color[2] = text_color.b; + v3.color[3] = text_color.a; + + result.vertices.push_back(v0); + result.vertices.push_back(v1); + result.vertices.push_back(v2); + result.vertices.push_back(v3); + + // 生成两个三角形的索引(逆时针) + // 三角形1: 左上 -> 右上 -> 右下 + result.indices.push_back(base_index + 0); + result.indices.push_back(base_index + 1); + result.indices.push_back(base_index + 2); + + // 三角形2: 左上 -> 右下 -> 左下 + result.indices.push_back(base_index + 0); + result.indices.push_back(base_index + 2); + result.indices.push_back(base_index + 3); + + base_index += 4; + + // 移动光标到下一个字符位置 + cursor_x += glyph.advance_x * scale; + } + + // 计算总宽度和高度 + result.total_width = cursor_x - position.x(); + result.total_height = max_y - min_y; + + return result; +} + +auto text_shaper::measure_text( + std::u32string_view text, + font_manager::font_id_t font_id, + float font_size +) -> text_metrics { + text_metrics result{0.0f, 0.0f}; + + if (text.empty()) { + return result; + } + + // 字体缩放因子(基准大小为48,与glyph_cache的默认glyph_size一致) + const float base_size = 48.0f; + const float scale = font_size / base_size; + + // 累计宽度 + float total_width = 0.0f; + + // 跟踪文本边界 + float min_y = 0.0f; + float max_y = 0.0f; + + for (char32_t codepoint : text) { + // 获取字形信息 + auto glyph_ptr = cache_.get_or_create_glyph(font_id, codepoint); + if (!glyph_ptr) { + // 如果字形不存在,跳过 + continue; + } + + const auto& glyph = *glyph_ptr; + + // 计算字形的屏幕空间尺寸 + const float glyph_y = -glyph.bearing_y * scale; + const float glyph_h = glyph.height * scale; + + // 更新边界 + min_y = std::min(min_y, glyph_y); + max_y = std::max(max_y, glyph_y + glyph_h); + + // 累加前进距离 + total_width += glyph.advance_x * scale; + } + + result.width = total_width; + result.height = max_y - min_y; + + return result; +} + +auto text_shaper::utf8_to_utf32(std::string_view utf8) -> std::u32string { + std::u32string result; + result.reserve(utf8.size()); // 最坏情况下每个字节一个字符 + + size_t i = 0; + while (i < utf8.size()) { + const auto byte = static_cast(utf8[i]); + char32_t codepoint = 0; + size_t bytes_to_read = 0; + + // 判断UTF-8序列长度 + if ((byte & 0x80) == 0x00) { + // 1字节序列:0xxxxxxx + codepoint = byte; + bytes_to_read = 1; + } else if ((byte & 0xE0) == 0xC0) { + // 2字节序列:110xxxxx 10xxxxxx + codepoint = byte & 0x1F; + bytes_to_read = 2; + } else if ((byte & 0xF0) == 0xE0) { + // 3字节序列:1110xxxx 10xxxxxx 10xxxxxx + codepoint = byte & 0x0F; + bytes_to_read = 3; + } else if ((byte & 0xF8) == 0xF0) { + // 4字节序列:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + codepoint = byte & 0x07; + bytes_to_read = 4; + } else { + // 无效的UTF-8序列,跳过 + ++i; + continue; + } + + // 检查是否有足够的字节 + if (i + bytes_to_read > utf8.size()) { + break; + } + + // 读取后续字节 + for (size_t j = 1; j < bytes_to_read; ++j) { + const auto continuation = static_cast(utf8[i + j]); + + // 验证后续字节格式(10xxxxxx) + if ((continuation & 0xC0) != 0x80) { + // 无效的UTF-8序列 + bytes_to_read = 0; + break; + } + + codepoint = (codepoint << 6) | (continuation & 0x3F); + } + + if (bytes_to_read > 0) { + result.push_back(codepoint); + } + + i += bytes_to_read > 0 ? bytes_to_read : 1; + } + + return result; +} + +} // namespace mirage::render::text \ No newline at end of file diff --git a/src/render/text/text_shaper.h b/src/render/text/text_shaper.h new file mode 100644 index 0000000..363e4d2 --- /dev/null +++ b/src/render/text/text_shaper.h @@ -0,0 +1,56 @@ +#pragma once + +#include "font_manager.h" +#include "glyph_cache.h" +#include "renderer/vertex_types.h" +#include "render_command.h" +#include "types.h" + +#include +#include + +namespace mirage::render::text { + +// 文本排版结果 +struct shaped_text { + std::vector vertices; + std::vector indices; + float total_width; + float total_height; +}; + +// 文本度量结果(仅包含尺寸信息,不生成顶点) +struct text_metrics { + float width; + float height; +}; + +class text_shaper { +public: + text_shaper(font_manager& font_mgr, glyph_cache& cache); + + // 排版文本,生成顶点数据 + auto shape_text( + std::u32string_view text, + font_manager::font_id_t font_id, + float font_size, + const vec2f_t& position, + const color& text_color + ) -> shaped_text; + + // 测量文本尺寸(不生成顶点,仅计算宽高) + auto measure_text( + std::u32string_view text, + font_manager::font_id_t font_id, + float font_size + ) -> text_metrics; + + // UTF-8转UTF-32 + static auto utf8_to_utf32(std::string_view utf8) -> std::u32string; + +private: + font_manager& font_mgr_; + glyph_cache& cache_; +}; + +} // namespace mirage::render::text \ No newline at end of file diff --git a/src/render/vulkan/context.cpp b/src/render/vulkan/context.cpp index 77683b3..4962cc6 100644 --- a/src/render/vulkan/context.cpp +++ b/src/render/vulkan/context.cpp @@ -21,7 +21,7 @@ namespace mirage { } } // namespace - auto render_context::create(const create_info& info) -> expected_t { + auto render_context::create(const create_info& info) -> expected_t { // 1. Initialize Dynamic Loader // The DynamicLoader loads the Vulkan library (e.g., vulkan-1.dll/libvulkan.so) static vk::detail::DynamicLoader dl; diff --git a/src/render/vulkan/context.h b/src/render/vulkan/context.h index 8a3410c..5326be1 100644 --- a/src/render/vulkan/context.h +++ b/src/render/vulkan/context.h @@ -1,5 +1,6 @@ #pragma once +#include "types.h" #include "vulkan_common.h" namespace mirage { @@ -14,7 +15,7 @@ namespace mirage { std::vector required_extensions; }; - static auto create(const create_info& info) -> expected_t; + static auto create(const create_info& info) -> expected_t; render_context() = default; ~render_context(); diff --git a/src/render/vulkan/device_manager.cpp b/src/render/vulkan/device_manager.cpp index d196146..8757a29 100644 --- a/src/render/vulkan/device_manager.cpp +++ b/src/render/vulkan/device_manager.cpp @@ -14,7 +14,7 @@ namespace mirage { return devices; } - auto device_manager::select_primary_device(vk::SurfaceKHR surface) const -> expected_t { + auto device_manager::select_primary_device(vk::SurfaceKHR surface) const -> expected_t { auto devices = enumerate_devices(); // Preference score map diff --git a/src/render/vulkan/device_manager.h b/src/render/vulkan/device_manager.h index 93c5a43..08c1d51 100644 --- a/src/render/vulkan/device_manager.h +++ b/src/render/vulkan/device_manager.h @@ -1,5 +1,6 @@ #pragma once +#include "types.h" #include "vulkan_common.h" namespace mirage { @@ -14,7 +15,7 @@ namespace mirage { * @return expected Selected device or error. */ [[nodiscard]] auto - select_primary_device(vk::SurfaceKHR surface = nullptr) const -> expected_t; + select_primary_device(vk::SurfaceKHR surface = nullptr) const -> expected_t; /** * @brief Enumerates all available physical devices. diff --git a/src/render/vulkan/logical_device.cpp b/src/render/vulkan/logical_device.cpp index 06eb0af..20d2c01 100644 --- a/src/render/vulkan/logical_device.cpp +++ b/src/render/vulkan/logical_device.cpp @@ -48,7 +48,7 @@ namespace mirage { } auto logical_device::create(vk::PhysicalDevice physical_device, - vk::SurfaceKHR surface) -> expected_t { + vk::SurfaceKHR surface) -> expected_t { // Validate physical device before using it if (!physical_device) { return std::unexpected(vk::Result::eErrorInitializationFailed); diff --git a/src/render/vulkan/logical_device.h b/src/render/vulkan/logical_device.h index cdf250f..69b7e39 100644 --- a/src/render/vulkan/logical_device.h +++ b/src/render/vulkan/logical_device.h @@ -1,5 +1,6 @@ #pragma once +#include "types.h" #include "vulkan_common.h" namespace mirage { @@ -16,7 +17,7 @@ namespace mirage { }; static auto create(vk::PhysicalDevice physical_device, - vk::SurfaceKHR surface = nullptr) -> expected_t; + vk::SurfaceKHR surface = nullptr) -> expected_t; logical_device() = default; ~logical_device(); diff --git a/src/render/vulkan/pipeline/compute_pipeline.cpp b/src/render/vulkan/pipeline/compute_pipeline.cpp index 340080e..2d89095 100644 --- a/src/render/vulkan/pipeline/compute_pipeline.cpp +++ b/src/render/vulkan/pipeline/compute_pipeline.cpp @@ -8,7 +8,7 @@ namespace mirage { const logical_device& device, vk::ShaderModule shader_module, std::span bindings - ) -> expected_t { + ) -> expected_t { vk::Device vk_device = device.get_handle(); // 1. Create Descriptor Set Layout @@ -122,7 +122,7 @@ namespace mirage { } auto compute_pipeline::create(const logical_device& device, const std::string& shader_path, - std::span bindings) -> expected_t { + std::span bindings) -> expected_t { auto module_ret = graphics_pipeline::load_shader_module(device, shader_path); if (!module_ret) return std::unexpected(module_ret.error()); @@ -137,7 +137,7 @@ namespace mirage { } auto compute_pipeline::create_from_spv(const logical_device& device, std::span spv_code, - std::span bindings) -> expected_t { + std::span bindings) -> expected_t { auto module_ret = graphics_pipeline::load_shader_module_from_memory(device, spv_code); if (!module_ret) return std::unexpected(module_ret.error()); @@ -151,7 +151,7 @@ namespace mirage { return result; } - auto compute_pipeline::allocate_descriptor_set(vk::DescriptorPool pool) const -> expected_t { + auto compute_pipeline::allocate_descriptor_set(vk::DescriptorPool pool) const -> expected_t { vk::DescriptorSetAllocateInfo alloc_info( pool, 1, &descriptor_set_layout_ diff --git a/src/render/vulkan/pipeline/compute_pipeline.h b/src/render/vulkan/pipeline/compute_pipeline.h index 81c8a92..30e53d5 100644 --- a/src/render/vulkan/pipeline/compute_pipeline.h +++ b/src/render/vulkan/pipeline/compute_pipeline.h @@ -12,9 +12,9 @@ namespace mirage { public: // Constructors / Factory static auto create(const logical_device& device, const std::string& shader_path, - std::span bindings = {}) -> expected_t; + std::span bindings = {}) -> expected_t; static auto create_from_spv(const logical_device& device, std::span spv_code, - std::span bindings = {}) -> expected_t; + std::span bindings = {}) -> expected_t; // Interface compute_pipeline(const logical_device& device, vk::Pipeline pipeline, vk::PipelineLayout layout, @@ -35,7 +35,7 @@ namespace mirage { // Descriptor Set Management // Allocates a descriptor set from the provided pool using the pipeline's layout - [[nodiscard]] auto allocate_descriptor_set(vk::DescriptorPool pool) const -> expected_t; + [[nodiscard]] auto allocate_descriptor_set(vk::DescriptorPool pool) const -> expected_t; // Updates a descriptor set to bind a buffer to a specific binding point void bind_buffer(vk::DescriptorSet set, uint32_t binding, const resource_manager::buffer_resource& buffer, diff --git a/src/render/vulkan/pipeline/graphics_pipeline.cpp b/src/render/vulkan/pipeline/graphics_pipeline.cpp index 29ff767..9c81fd5 100644 --- a/src/render/vulkan/pipeline/graphics_pipeline.cpp +++ b/src/render/vulkan/pipeline/graphics_pipeline.cpp @@ -49,7 +49,7 @@ namespace mirage { } auto graphics_pipeline::load_shader_module(const logical_device& device, - const std::string& filepath) -> expected_t { + const std::string& filepath) -> expected_t { std::ifstream file(filepath, std::ios::ate | std::ios::binary); if (!file.is_open()) { @@ -74,7 +74,7 @@ namespace mirage { auto graphics_pipeline::load_shader_module_from_memory(const logical_device& device, std::span code) -> expected_t< - vk::ShaderModule> { + vk::ShaderModule, vk::Result> { vk::ShaderModuleCreateInfo create_info( {}, code.size() * sizeof(uint32_t), diff --git a/src/render/vulkan/pipeline/graphics_pipeline.h b/src/render/vulkan/pipeline/graphics_pipeline.h index 41e8801..1835219 100644 --- a/src/render/vulkan/pipeline/graphics_pipeline.h +++ b/src/render/vulkan/pipeline/graphics_pipeline.h @@ -25,9 +25,9 @@ namespace mirage { // Helper to load shader module static auto load_shader_module(const logical_device& device, - const std::string& filepath) -> expected_t; + const std::string& filepath) -> expected_t; static auto load_shader_module_from_memory(const logical_device& device, - std::span code) -> expected_t; + std::span code) -> expected_t; private: vk::Device device_; diff --git a/src/render/vulkan/pipeline/pipeline_builder.cpp b/src/render/vulkan/pipeline/pipeline_builder.cpp index a86e715..fc01330 100644 --- a/src/render/vulkan/pipeline/pipeline_builder.cpp +++ b/src/render/vulkan/pipeline/pipeline_builder.cpp @@ -105,7 +105,7 @@ namespace mirage { } auto pipeline_builder::build(vk::PipelineLayout layout, vk::RenderPass render_pass, - uint32_t subpass) -> expected_t { + uint32_t subpass) -> expected_t { vk::PipelineViewportStateCreateInfo viewport_state({}, 1, &viewport_, 1, &scissor_); vk::PipelineColorBlendStateCreateInfo color_blending( diff --git a/src/render/vulkan/pipeline/pipeline_builder.h b/src/render/vulkan/pipeline/pipeline_builder.h index bcaae25..a201747 100644 --- a/src/render/vulkan/pipeline/pipeline_builder.h +++ b/src/render/vulkan/pipeline/pipeline_builder.h @@ -23,7 +23,7 @@ namespace mirage { // Currently requires an explicit layout and renderpass auto build(vk::PipelineLayout layout, vk::RenderPass render_pass, - uint32_t subpass = 0) -> expected_t; + uint32_t subpass = 0) -> expected_t; private: const logical_device& device_; diff --git a/src/render/vulkan/resource_manager.cpp b/src/render/vulkan/resource_manager.cpp index 0a81455..dc302c8 100644 --- a/src/render/vulkan/resource_manager.cpp +++ b/src/render/vulkan/resource_manager.cpp @@ -30,7 +30,7 @@ namespace mirage { vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties - ) -> expected_t { + ) -> expected_t { // Validate input parameters if (size == 0) { return std::unexpected(vk::Result::eErrorInitializationFailed); @@ -87,7 +87,7 @@ namespace mirage { vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::ImageAspectFlags aspect_flags - ) -> expected_t { + ) -> expected_t { VkImageCreateInfo image_info = {}; image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; image_info.imageType = VK_IMAGE_TYPE_2D; @@ -141,7 +141,7 @@ namespace mirage { } auto resource_manager::create_image_view(vk::Image image, vk::Format format, - vk::ImageAspectFlags aspect_flags) -> expected_t { + vk::ImageAspectFlags aspect_flags) -> expected_t { vk::ImageViewCreateInfo view_info( {}, image, diff --git a/src/render/vulkan/resource_manager.h b/src/render/vulkan/resource_manager.h index 87c6f60..95a0d29 100644 --- a/src/render/vulkan/resource_manager.h +++ b/src/render/vulkan/resource_manager.h @@ -32,7 +32,7 @@ namespace mirage { vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties - ) -> expected_t; + ) -> expected_t; [[nodiscard]] auto create_image( uint32_t width, @@ -42,7 +42,7 @@ namespace mirage { vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::ImageAspectFlags aspect_flags = vk::ImageAspectFlagBits::eColor - ) -> expected_t; + ) -> expected_t; void destroy_buffer(const buffer_resource& resource); void destroy_image(const image_resource& resource); @@ -52,7 +52,7 @@ namespace mirage { vk::Image image, vk::Format format, vk::ImageAspectFlags aspect_flags - ) -> expected_t; + ) -> expected_t; private: vk::Device device_; diff --git a/src/render/vulkan/swapchain.cpp b/src/render/vulkan/swapchain.cpp index 6c3ccc2..9c9bad0 100644 --- a/src/render/vulkan/swapchain.cpp +++ b/src/render/vulkan/swapchain.cpp @@ -256,7 +256,7 @@ namespace mirage { uint32_t height, bool vsync, resource_manager& resource_manager - ) -> expected_t { + ) -> expected_t { // 使用旧的 API 调用新的重载版本 config swapchain_config; swapchain_config.vsync = vsync; @@ -274,7 +274,7 @@ namespace mirage { uint32_t height, const config& swapchain_config, resource_manager& resource_manager - ) -> expected_t { + ) -> expected_t { auto support = query_support(physical_device, surface); auto surface_format = choose_surface_format(support.formats); @@ -372,7 +372,7 @@ namespace mirage { return new_swapchain; } - auto swapchain::recreate(uint32_t width, uint32_t height, bool vsync, bool adaptive_sync) -> expected_t { + auto swapchain::recreate(uint32_t width, uint32_t height, bool vsync, bool adaptive_sync) -> expected_t { // 使用旧的 API 调用新的重载版本 config new_config; new_config.vsync = vsync; @@ -382,7 +382,7 @@ namespace mirage { return recreate(width, height, new_config); } - auto swapchain::recreate(uint32_t width, uint32_t height, const config& swapchain_config) -> expected_t { + auto swapchain::recreate(uint32_t width, uint32_t height, const config& swapchain_config) -> expected_t { // 1. 等待设备空闲 (void)device_.waitIdle(); @@ -486,7 +486,7 @@ namespace mirage { } auto swapchain::acquire_next_image(vk::Semaphore semaphore, vk::Fence fence, - uint64_t timeout) -> expected_t { + uint64_t timeout) -> expected_t { auto [result, image_index] = device_.acquireNextImageKHR(swapchain_, timeout, semaphore, fence); if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR && result != vk::Result::eTimeout) { return std::unexpected(result); @@ -510,7 +510,7 @@ namespace mirage { return present_queue_.presentKHR(present_info); } - auto swapchain::create_default_render_pass() const -> expected_t { + auto swapchain::create_default_render_pass() const -> expected_t { vk::AttachmentDescription color_attachment( {}, format_, diff --git a/src/render/vulkan/swapchain.h b/src/render/vulkan/swapchain.h index 527876a..f7569ef 100644 --- a/src/render/vulkan/swapchain.h +++ b/src/render/vulkan/swapchain.h @@ -36,7 +36,7 @@ namespace mirage { uint32_t height, bool vsync, resource_manager& resource_manager - ) -> expected_t; + ) -> expected_t; static auto create( const logical_device& logical_device, @@ -46,7 +46,7 @@ namespace mirage { uint32_t height, const config& swapchain_config, resource_manager& resource_manager - ) -> expected_t; + ) -> expected_t; swapchain() = default; ~swapchain(); @@ -59,12 +59,12 @@ namespace mirage { swapchain(swapchain&&) noexcept; swapchain& operator=(swapchain&&) noexcept; - auto recreate(uint32_t width, uint32_t height, bool vsync, bool adaptive_sync = false) -> expected_t; - auto recreate(uint32_t width, uint32_t height, const config& swapchain_config) -> expected_t; + auto recreate(uint32_t width, uint32_t height, bool vsync, bool adaptive_sync = false) -> expected_t; + auto recreate(uint32_t width, uint32_t height, const config& swapchain_config) -> expected_t; [[nodiscard]] auto acquire_next_image(vk::Semaphore semaphore, vk::Fence fence = nullptr, uint64_t timeout = std::numeric_limits::max()) -> expected_t< - uint32_t>; + uint32_t, vk::Result>; [[nodiscard]] auto present(uint32_t image_index, vk::Semaphore wait_semaphore) -> vk::Result; [[nodiscard]] auto get_handle() const { return swapchain_; } @@ -76,7 +76,7 @@ namespace mirage { [[nodiscard]] auto is_vrr_enabled() const -> bool; [[nodiscard]] auto get_present_mode() const -> vk::PresentModeKHR; - [[nodiscard]] auto create_default_render_pass() const -> expected_t; + [[nodiscard]] auto create_default_render_pass() const -> expected_t; private: explicit swapchain( diff --git a/src/render/vulkan/vulkan_common.h b/src/render/vulkan/vulkan_common.h index e3a4fad..ad21a3e 100644 --- a/src/render/vulkan/vulkan_common.h +++ b/src/render/vulkan/vulkan_common.h @@ -30,10 +30,6 @@ #include namespace mirage { - // Using std::expected for error handling as per design doc - template - using expected_t = std::expected; - struct binding_info { uint32_t binding; vk::DescriptorType type; diff --git a/src/widget/modifier_helper.h b/src/widget/modifier_helper.h index aafbb1a..a73ef41 100644 --- a/src/widget/modifier_helper.h +++ b/src/widget/modifier_helper.h @@ -1,10 +1,6 @@ #pragma once namespace mirage { - // ============================================================================ - // 辅助函数 - // ============================================================================ - // ============================================================================ // 布局属性存储(用于 widget_builder) // ============================================================================ diff --git a/src/widget/render_collector.cpp b/src/widget/render_collector.cpp index e60f1a5..5764838 100644 --- a/src/widget/render_collector.cpp +++ b/src/widget/render_collector.cpp @@ -37,7 +37,7 @@ namespace mirage { void render_collector::submit_text(const vec2f_t& position, std::string_view text, const color& text_color, float font_size, uint32_t font_id, int32_t z_order) { - text_command tc(position, std::string(text), text_color, font_size); + text_command tc(position, std::string(text), text_color, font_size, font_id); tc.order = render_order::at(current_z_order_++, get_current_clip()); render_commands_.emplace_back(std::move(tc)); } diff --git a/src/widget/widget_base.h b/src/widget/widget_base.h index ac6426e..9bc3172 100644 --- a/src/widget/widget_base.h +++ b/src/widget/widget_base.h @@ -307,4 +307,43 @@ namespace mirage { }; } // namespace mirage +#define WIDGET_ATTR(type, name) \ +private: \ + type name##_; \ +public: \ + [[nodiscard]] auto name() const -> type { \ + return name##_; \ + } \ + auto& name(const type& value) { \ + name##_ = value; \ + return *this; \ + } + +#define WIDGET_RENDER_ATTR(type, name) \ +private: \ + type name##_; \ +public: \ + [[nodiscard]] auto name() const -> type { \ + return name##_; \ + } \ + auto& name(const type& value) { \ + name##_ = value; \ + mark_render_dirty(); \ + return *this; \ + } + +#define WIDGET_MEASURE_ATTR(type, name) \ +private: \ + type name##_; \ +public: \ + [[nodiscard]] auto name() const -> type { \ + return name##_; \ + } \ + auto& name(const type& value) { \ + name##_ = value; \ + mark_measure_dirty(); \ + return *this; \ + } + + #include "modifier_helper.h" diff --git a/src/widget/widget_context.h b/src/widget/widget_context.h index 7fdfb8f..51380e2 100644 --- a/src/widget/widget_context.h +++ b/src/widget/widget_context.h @@ -11,6 +11,10 @@ namespace mirage { // 前向声明 using layout_write_context = state::widget_state_store::layout_write_context; +namespace render::text { + class text_shaper; +} + /// @brief 控件上下文 /// /// 提供控件访问外部服务的入口,包括: @@ -19,7 +23,8 @@ using layout_write_context = state::widget_state_store::layout_write_context; /// - 帧信息 class widget_context { public: - explicit widget_context(state::widget_state_store& store) : store_(store) {} + explicit widget_context(state::widget_state_store& store, render::text::text_shaper& shaper) + : store_(store), text_shaper_(shaper) {} // ======================================================================== // 状态存储访问 @@ -92,11 +97,21 @@ public: [[nodiscard]] const widget::viewport_cache_config& get_viewport_cache_config() const noexcept { return viewport_cache_config_; } + + // ======================================================================== + // 文本渲染服务 + // ======================================================================== + + /// @brief 获取text_shaper(用于文本度量和排版) + [[nodiscard]] render::text::text_shaper& get_text_shaper() const noexcept { + return text_shaper_; + } private: state::widget_state_store& store_; widget::viewport_cache viewport_cache_; widget::viewport_cache_config viewport_cache_config_; + render::text::text_shaper& text_shaper_; }; } // namespace mirage \ No newline at end of file diff --git a/src/widget/widgets/imager.h b/src/widget/widgets/imager.h index 84fcc89..261cf81 100644 --- a/src/widget/widgets/imager.h +++ b/src/widget/widgets/imager.h @@ -24,7 +24,7 @@ namespace mirage { } imager(uint32_t texture_id, const vec2f_t& source_size, scale_mode mode) - : texture_id_(texture_id), source_size_(source_size), scale_mode_(mode) { + : texture_id_(texture_id), source_size_(source_size), scale_(mode) { } // ======================================================================== @@ -44,14 +44,14 @@ namespace mirage { // fill/none 模式:请求 source_size // contain/scale_down 模式:在可用空间内按比例缩放 // cover 模式:请求 source_size(渲染时会超出) - switch (scale_mode_) { + switch (scale_) { case scale_mode::contain: case scale_mode::scale_down: { const float scale = std::min( available_size.x() / source_size_.x(), available_size.y() / source_size_.y() ); - if (scale_mode_ == scale_mode::scale_down && scale >= 1.0f) { + if (scale_ == scale_mode::scale_down && scale >= 1.0f) { return source_size_; } return vec2f_t{source_size_.x() * scale, source_size_.y() * scale}; @@ -69,7 +69,7 @@ namespace mirage { // cover 模式需要裁剪超出容器的部分 // 使用容器的布局边界作为裁剪区域 - const bool needs_clip = (scale_mode_ == scale_mode::cover); + const bool needs_clip = (scale_ == scale_mode::cover); if (needs_clip) { // 裁剪边界应该是容器的实际布局区域 @@ -114,55 +114,11 @@ namespace mirage { // 属性设置(链式调用) // ======================================================================== - auto& texture(uint32_t id) { - texture_id_ = id; - mark_render_dirty(); - return *this; - } - - [[nodiscard]] uint32_t get_texture() const { return texture_id_; } - - auto& fixed_size(float w, float h) { - fixed_size_ = vec2f_t{w, h}; - return *this; - } - - auto& fixed_size(const vec2f_t& size) { - fixed_size_ = size; - return *this; - } - - [[nodiscard]] const vec2f_t& get_fixed_size() const { return fixed_size_; } - - auto& source_size(float w, float h) { - source_size_ = vec2f_t{w, h}; - mark_render_dirty(); - return *this; - } - - auto& source_size(const vec2f_t& size) { - source_size_ = size; - mark_render_dirty(); - return *this; - } - - [[nodiscard]] const vec2f_t& get_source_size() const { return source_size_; } - - auto& scale(scale_mode mode) { - scale_mode_ = mode; - mark_render_dirty(); - return *this; - } - - [[nodiscard]] scale_mode get_scale_mode() const { return scale_mode_; } - - auto& tint_color(const color& c) { - tint_color_ = c; - mark_render_dirty(); - return *this; - } - - [[nodiscard]] const color& get_tint_color() const { return tint_color_; } + WIDGET_RENDER_ATTR(uint32_t, texture_id) + WIDGET_MEASURE_ATTR(vec2f_t, fixed_size) + WIDGET_MEASURE_ATTR(vec2f_t, source_size) + WIDGET_MEASURE_ATTR(scale_mode, scale) + WIDGET_RENDER_ATTR(color, tint) private: /// @brief 计算缩放后的尺寸 @@ -172,7 +128,7 @@ namespace mirage { const float avail_w = available_size.x(); const float avail_h = available_size.y(); - switch (scale_mode_) { + switch (scale_) { case scale_mode::fill: return available_size; @@ -210,7 +166,7 @@ namespace mirage { const vec2f_t container_size = vec2f_t(state->size()); // 如果没有源尺寸或使用 fill 模式,直接返回容器尺寸 - if (source_size_.x() <= 0 || source_size_.y() <= 0 || scale_mode_ == scale_mode::fill) { + if (source_size_.x() <= 0 || source_size_.y() <= 0 || scale_ == scale_mode::fill) { return {container_pos, container_size}; } @@ -223,11 +179,5 @@ namespace mirage { return {render_pos, scaled_size}; } - - uint32_t texture_id_ = 0; - vec2f_t fixed_size_{0, 0}; - vec2f_t source_size_{0, 0}; ///< 源图片尺寸(用于计算缩放比例) - scale_mode scale_mode_ = scale_mode::fill; - color tint_color_ = color::white(); }; } // namespace mirage diff --git a/src/widget/widgets/text_widget.h b/src/widget/widgets/text_widget.h index 08a006a..3f9989a 100644 --- a/src/widget/widgets/text_widget.h +++ b/src/widget/widgets/text_widget.h @@ -1,6 +1,7 @@ #pragma once #include "leaf_widget_base.h" #include "render_collector.h" +#include "text/text_shaper.h" #include #include @@ -8,12 +9,13 @@ namespace mirage { /// @brief 文本控件 - 显示文本内容 class text_widget : public leaf_widget_base { public: - text_widget() = default; - - explicit text_widget(std::string_view text) : text_(text) { + text_widget() : font_id_(0), font_size_(14.f), text_color_(color::white()), text_("") { } - text_widget(std::string_view text, float font_size) : text_(text), font_size_(font_size) { + explicit text_widget(std::string_view text) : font_size_(14.f), text_(text) { + } + + text_widget(std::string_view text, float font_size) : font_size_(font_size), text_(text) { } // ======================================================================== @@ -22,11 +24,40 @@ namespace mirage { [[nodiscard]] auto do_measure(const vec2f_t& available_size) const -> vec2f_t override { (void)available_size; - // 简化实现:基于字符数估算大小 - // 实际实现应该使用字体度量 - float width = static_cast(text_.size()) * font_size_ * 0.6f; - float height = font_size_ * 1.2f; - return vec2f_t{width, height}; + + // 尝试使用text_shaper进行精确度量 + if (auto* ctx = get_context()) { + if (!text_.empty()) { + // 转换UTF-8到UTF-32 + auto utf32_text = render::text::text_shaper::utf8_to_utf32(text_); + + // 使用text_shaper进行精确度量 + auto metrics = ctx->get_text_shaper().measure_text(utf32_text, font_id_, font_size_); + return vec2f_t{metrics.width, metrics.height}; + } + // 空文本返回最小高度(基于字体大小) + return vec2f_t{0.0f, font_size_ * 1.2f}; + } + + // 回退到估算算法(当text_shaper不可用时) + // 使用更精确的字体度量常数(基于常见等宽字体) + // - 平均字符宽度约为字体大小的0.5-0.6倍(拉丁字符) + // - 中文字符宽度约为字体大小的1.0倍 + // - 行高约为字体大小的1.2-1.5倍 + + if (text_.empty()) { + // 空文本返回最小高度 + return vec2f_t{0.0f, font_size_ * 1.2f}; + } + + // 估算字符数(UTF-8编码,中文字符占3个字节) + // 这里简化处理:假设平均字符宽度为font_size的0.55倍 + float estimated_width = static_cast(text_.size()) * font_size_ * 0.55f; + + // 行高为字体大小的1.2倍(包含基线以上和以下的空间) + float estimated_height = font_size_ * 1.2f; + + return vec2f_t{estimated_width, estimated_height}; } uint32_t build_render_command(render_collector& collector, uint32_t z_order) const override { @@ -34,8 +65,9 @@ namespace mirage { return z_order; const auto state_opt = get_layout_state(); - if (!state_opt.has_value()) return z_order; - + if (!state_opt.has_value()) + return z_order; + const auto& state = state_opt.value(); collector.submit_text( state.global_pos(), @@ -60,42 +92,9 @@ namespace mirage { // 属性设置(链式调用) // ======================================================================== - auto& text(std::string_view t) { - text_ = t; - mark_render_dirty(); - return *this; - } - - [[nodiscard]] const std::string& get_text() const { return text_; } - - auto& text_color(const color& c) { - text_color_ = c; - mark_render_dirty(); - return *this; - } - - [[nodiscard]] const color& get_text_color() const { return text_color_; } - - auto& font_size(float size) { - font_size_ = size; - mark_render_dirty(); - return *this; - } - - [[nodiscard]] float get_font_size() const { return font_size_; } - - auto& font_id(uint32_t id) { - font_id_ = id; - mark_render_dirty(); - return *this; - } - - [[nodiscard]] uint32_t get_font_id() const { return font_id_; } - - private: - std::string text_; - color text_color_ = color::white(); - float font_size_ = 14.0f; - uint32_t font_id_ = 0; + WIDGET_RENDER_ATTR(uint32_t, font_id) + WIDGET_RENDER_ATTR(float, font_size) + WIDGET_RENDER_ATTR(color, text_color) + WIDGET_MEASURE_ATTR(std::string, text) }; } // namespace mirage diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c8bd416..8957b31 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,8 +7,6 @@ find_package(GTest CONFIG REQUIRED) include(GoogleTest) add_subdirectory(render_tree) -add_subdirectory(render_tree_builder) -add_subdirectory(render_tree_integration) add_subdirectory(test_refactor_syntax) add_subdirectory(deferred_pass_manager) diff --git a/tests/render_tree_builder/CMakeLists.txt b/tests/render_tree_builder/CMakeLists.txt deleted file mode 100644 index b9d012a..0000000 --- a/tests/render_tree_builder/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -project(test_render_tree_builder) - -simple_executable() - -target_link_libraries(${PROJECT_NAME} PUBLIC - GTest::gtest - GTest::gtest_main - render_core - common -) \ No newline at end of file diff --git a/tests/render_tree_builder/test_render_tree_builder.cpp b/tests/render_tree_builder/test_render_tree_builder.cpp deleted file mode 100644 index 6f5bd62..0000000 --- a/tests/render_tree_builder/test_render_tree_builder.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include -#include "pipeline/render_tree_builder.h" -#include "render_command.h" - -using namespace mirage; -using namespace mirage::render; - -TEST(RenderTreeBuilderTest, BuildEmpty) { - std::vector commands; - render_tree_builder builder; - auto tree = builder.build(commands); - - ASSERT_NE(tree.get_root(), nullptr); - EXPECT_EQ(tree.get_root()->get_type(), render_node_type::group); - EXPECT_EQ(static_cast(tree.get_root())->get_child_count(), 0); -} - -TEST(RenderTreeBuilderTest, BuildSimpleGeometry) { - std::vector commands; - render_command_builder builder; - builder.draw_rectangle({0, 0}, {100, 100}, color::red()); - - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - auto* root = static_cast(tree.get_root()); - ASSERT_EQ(root->get_child_count(), 1); - - auto* child = root->get_children()[0].get(); - EXPECT_EQ(child->get_type(), render_node_type::geometry); - - auto* geo_node = static_cast(child); - EXPECT_FALSE(geo_node->get_batches().empty()); -} - -TEST(RenderTreeBuilderTest, BuildMask) { - std::vector commands; - render_command_builder builder; - - mask_params params; - params.type = mask_type::rect; - - builder.begin_mask(params, aabb2d_t(vec2f_t(0.0f, 0.0f), vec2f_t(100.0f, 100.0f))); - builder.draw_rectangle({10, 10}, {50, 50}, color::blue()); - builder.end_mask(); - - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - auto* root = static_cast(tree.get_root()); - ASSERT_EQ(root->get_child_count(), 1); - - auto* child = root->get_children()[0].get(); - EXPECT_EQ(child->get_type(), render_node_type::mask); - - auto* mask_node_ptr = static_cast(child); - ASSERT_EQ(mask_node_ptr->get_child_count(), 1); - - auto* mask_child = mask_node_ptr->get_children()[0].get(); - EXPECT_EQ(mask_child->get_type(), render_node_type::geometry); -} - -TEST(RenderTreeBuilderTest, BuildPostEffect) { - std::vector commands; - render_command_builder builder; - - builder.draw_blur({0, 0}, {100, 100}, 10.0f); - - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - auto* root = static_cast(tree.get_root()); - ASSERT_EQ(root->get_child_count(), 1); - - auto* child = root->get_children()[0].get(); - EXPECT_EQ(child->get_type(), render_node_type::post_effect); -} - -TEST(RenderTreeBuilderTest, BuildNested) { - std::vector commands; - render_command_builder builder; - - // Root geometry - builder.draw_rectangle({0, 0}, {200, 200}, color::white()); - - // Mask - mask_params params; - builder.begin_mask(params, aabb2d_t(vec2f_t(0.0f, 0.0f), vec2f_t(100.0f, 100.0f))); - // Inside mask - builder.draw_rectangle({10, 10}, {50, 50}, color::red()); - - // Nested Post Effect - builder.draw_blur({10, 10}, {50, 50}); - builder.end_mask(); - - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - auto* root = static_cast(tree.get_root()); - // Should have 2 children: 1 geometry (root rect), 1 mask - // Note: The order depends on z-order. Default z-order is 0 for all. - // Stable sort preserves order. - ASSERT_EQ(root->get_child_count(), 2); - - EXPECT_EQ(root->get_children()[0]->get_type(), render_node_type::geometry); - EXPECT_EQ(root->get_children()[1]->get_type(), render_node_type::mask); - - auto* mask_node_ptr = static_cast(root->get_children()[1].get()); - // Mask should have 2 children: 1 geometry, 1 post effect - ASSERT_EQ(mask_node_ptr->get_child_count(), 2); - EXPECT_EQ(mask_node_ptr->get_children()[0]->get_type(), render_node_type::geometry); - EXPECT_EQ(mask_node_ptr->get_children()[1]->get_type(), render_node_type::post_effect); -} \ No newline at end of file diff --git a/tests/render_tree_integration/CMakeLists.txt b/tests/render_tree_integration/CMakeLists.txt deleted file mode 100644 index a6e7c5b..0000000 --- a/tests/render_tree_integration/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -project(test_render_tree_integration) - -simple_executable() - -target_link_libraries(${PROJECT_NAME} PUBLIC - GTest::gtest - GTest::gtest_main - render_core - common - widget_core -) \ No newline at end of file diff --git a/tests/render_tree_integration/test_render_tree_integration.cpp b/tests/render_tree_integration/test_render_tree_integration.cpp deleted file mode 100644 index 03c5dd0..0000000 --- a/tests/render_tree_integration/test_render_tree_integration.cpp +++ /dev/null @@ -1,854 +0,0 @@ -#include -#include "pipeline/render_tree_builder.h" -#include "render_command.h" - -using namespace mirage; -using namespace mirage::render; - -// ============================================================================ -// 测试 1: 单层 mask 测试 -// ============================================================================ - -/// @brief 验证基本遮罩功能 -/// 创建包含 mask_begin、geometry、mask_end 的命令序列,验证构建的树结构正确 -TEST(RenderTreeIntegrationTest, SingleMask) { - render_command_builder builder; - - // 创建遮罩区域 - mask_params params; - params.type = mask_type::rect; - - builder.begin_mask(params, aabb2d_t(vec2f_t(0.0f, 0.0f), vec2f_t(100.0f, 100.0f))); - builder.draw_rectangle({10, 10}, {50, 50}, color::red()); - builder.draw_rectangle({20, 20}, {30, 30}, color::blue()); - builder.end_mask(); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - ASSERT_EQ(root->get_child_count(), 1); - - // 验证 mask 节点 - auto* mask_node_ptr = root->get_children()[0].get(); - EXPECT_EQ(mask_node_ptr->get_type(), render_node_type::mask); - - auto* mask = static_cast(mask_node_ptr); - ASSERT_EQ(mask->get_child_count(), 1); - - // 验证 mask 内的 geometry 节点 - auto* geo_node = mask->get_children()[0].get(); - EXPECT_EQ(geo_node->get_type(), render_node_type::geometry); - - auto* geo = static_cast(geo_node); - // 相邻的几何命令会被合并到一个批次中 - EXPECT_FALSE(geo->get_batches().empty()); -} - -// ============================================================================ -// 测试 2: 单层 post_effect 测试 -// ============================================================================ - -/// @brief 验证基本后效功能 -/// 创建包含 post_effect 和 geometry 的命令序列,验证构建的树结构正确 -TEST(RenderTreeIntegrationTest, SinglePostEffect) { - render_command_builder builder; - - // 创建后效区域 - builder.draw_blur({0, 0}, {100, 100}, 10.0f); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - ASSERT_EQ(root->get_child_count(), 1); - - // 验证 post_effect 节点 - auto* effect_node = root->get_children()[0].get(); - EXPECT_EQ(effect_node->get_type(), render_node_type::post_effect); - - auto* post_effect = static_cast(effect_node); - EXPECT_EQ(post_effect->get_child_count(), 0); // 后效节点没有子节点 -} - -// ============================================================================ -// 测试 3: mask 内 post_effect 测试 -// ============================================================================ - -/// @brief 验证遮罩内部有后效 -/// 创建嵌套结构:mask 包含 post_effect,验证树结构正确表达嵌套关系 -TEST(RenderTreeIntegrationTest, PostEffectInsideMask) { - render_command_builder builder; - - // 创建遮罩区域 - mask_params params; - params.type = mask_type::rounded_rect; - params.corner_radii = rect_corner_radius(10.0f); - - builder.begin_mask(params, aabb2d_t(vec2f_t(0.0f, 0.0f), vec2f_t(200.0f, 200.0f))); - // 在遮罩内绘制几何 - builder.draw_rectangle({10, 10}, {80, 80}, color::green()); - - // 在遮罩内应用后效 - builder.draw_blur({10, 10}, {80, 80}, 5.0f); - builder.end_mask(); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - ASSERT_EQ(root->get_child_count(), 1); - - // 验证 mask 节点 - auto* mask_node_ptr = root->get_children()[0].get(); - EXPECT_EQ(mask_node_ptr->get_type(), render_node_type::mask); - - auto* mask = static_cast(mask_node_ptr); - ASSERT_EQ(mask->get_child_count(), 2); // geometry + post_effect - - // 验证第一个子节点是 geometry - EXPECT_EQ(mask->get_children()[0]->get_type(), render_node_type::geometry); - - // 验证第二个子节点是 post_effect - EXPECT_EQ(mask->get_children()[1]->get_type(), render_node_type::post_effect); -} - -// ============================================================================ -// 测试 4: post_effect 内 mask 测试 -// ============================================================================ - -/// @brief 验证后效区域内有遮罩 -/// 创建嵌套结构:post_effect 包含 mask,验证树结构正确表达嵌套关系 -TEST(RenderTreeIntegrationTest, MaskInsidePostEffect) { - render_command_builder builder; - - // 创建后效区域 - builder.draw_vignette({0, 0}, {300, 300}, 0.6f, 0.4f); - - // 在后效区域内创建遮罩 - mask_params params; - params.type = mask_type::circle; - - builder.begin_mask(params, aabb2d_t(vec2f_t(50.0f, 50.0f), vec2f_t(150.0f, 150.0f))); - builder.draw_rectangle({60, 60}, {80, 80}, color::from_rgba(255, 255, 0)); // yellow - builder.end_mask(); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - ASSERT_EQ(root->get_child_count(), 2); // post_effect + mask - - // 验证第一个子节点是 post_effect - EXPECT_EQ(root->get_children()[0]->get_type(), render_node_type::post_effect); - - // 验证第二个子节点是 mask - auto* mask_node_ptr = root->get_children()[1].get(); - EXPECT_EQ(mask_node_ptr->get_type(), render_node_type::mask); - - auto* mask = static_cast(mask_node_ptr); - ASSERT_EQ(mask->get_child_count(), 1); - EXPECT_EQ(mask->get_children()[0]->get_type(), render_node_type::geometry); -} - -// ============================================================================ -// 测试 5: 多层嵌套测试 -// ============================================================================ - -/// @brief 验证 mask 和 post_effect 任意组合 -/// 创建复杂嵌套:mask -> post_effect -> mask,验证深度嵌套的正确性 -TEST(RenderTreeIntegrationTest, DeepNesting) { - render_command_builder builder; - - // 外层遮罩 - mask_params outer_mask; - outer_mask.type = mask_type::rect; - - builder.begin_mask(outer_mask, aabb2d_t(vec2f_t(0.0f, 0.0f), vec2f_t(400.0f, 400.0f))); - // 外层遮罩内的几何 - builder.draw_rectangle({10, 10}, {100, 100}, color::white()); - - // 外层遮罩内的后效 - builder.draw_blur({50, 50}, {200, 200}, 8.0f); - - // 内层遮罩(在外层遮罩内) - mask_params inner_mask; - inner_mask.type = mask_type::circle; - - builder.begin_mask(inner_mask, aabb2d_t(vec2f_t(100.0f, 100.0f), vec2f_t(200.0f, 200.0f))); - // 内层遮罩内的几何 - builder.draw_rectangle({120, 120}, {60, 60}, color::red()); - - // 内层遮罩内的后效 - builder.draw_chromatic_aberration({120, 120}, {60, 60}, 3.0f); - builder.end_mask(); - - // 外层遮罩内的更多几何 - builder.draw_rectangle({250, 250}, {100, 100}, color::blue()); - builder.end_mask(); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - ASSERT_EQ(root->get_child_count(), 1); - - // 验证外层 mask - auto* outer_mask_node = static_cast(root->get_children()[0].get()); - EXPECT_EQ(outer_mask_node->get_type(), render_node_type::mask); - ASSERT_EQ(outer_mask_node->get_child_count(), 4); // geo + post_effect + mask + geo - - // 验证外层 mask 的第一个子节点是 geometry - EXPECT_EQ(outer_mask_node->get_children()[0]->get_type(), render_node_type::geometry); - - // 验证外层 mask 的第二个子节点是 post_effect - EXPECT_EQ(outer_mask_node->get_children()[1]->get_type(), render_node_type::post_effect); - - // 验证外层 mask 的第三个子节点是内层 mask - auto* inner_mask_node = static_cast(outer_mask_node->get_children()[2].get()); - EXPECT_EQ(inner_mask_node->get_type(), render_node_type::mask); - ASSERT_EQ(inner_mask_node->get_child_count(), 2); // geo + post_effect - - // 验证内层 mask 的子节点 - EXPECT_EQ(inner_mask_node->get_children()[0]->get_type(), render_node_type::geometry); - EXPECT_EQ(inner_mask_node->get_children()[1]->get_type(), render_node_type::post_effect); - - // 验证外层 mask 的第四个子节点是 geometry - EXPECT_EQ(outer_mask_node->get_children()[3]->get_type(), render_node_type::geometry); -} - -// ============================================================================ -// 测试 6: 多个独立 mask 测试 -// ============================================================================ - -/// @brief 验证多个独立遮罩不会互相影响 -/// 创建多个独立的 mask 区域,验证它们在树中是独立的兄弟节点 -TEST(RenderTreeIntegrationTest, MultipleSiblingMasks) { - render_command_builder builder; - - // 第一个独立遮罩 - mask_params mask1; - mask1.type = mask_type::rect; - - builder.begin_mask(mask1, aabb2d_t(vec2f_t(0.0f, 0.0f), vec2f_t(100.0f, 100.0f))); - builder.draw_rectangle({10, 10}, {80, 80}, color::red()); - builder.end_mask(); - - // 第二个独立遮罩 - mask_params mask2; - mask2.type = mask_type::circle; - - builder.begin_mask(mask2, aabb2d_t(vec2f_t(150.0f, 0.0f), vec2f_t(250.0f, 100.0f))); - builder.draw_rectangle({160, 10}, {80, 80}, color::green()); - builder.end_mask(); - - // 第三个独立遮罩 - mask_params mask3; - mask3.type = mask_type::rounded_rect; - mask3.corner_radii = rect_corner_radius(15.0f); - - builder.begin_mask(mask3, aabb2d_t(vec2f_t(300.0f, 0.0f), vec2f_t(400.0f, 100.0f))); - builder.draw_rectangle({310, 10}, {80, 80}, color::blue()); - builder.end_mask(); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - ASSERT_EQ(root->get_child_count(), 3); // 三个独立的 mask - - // 验证所有子节点都是 mask - for (size_t i = 0; i < 3; ++i) { - auto* mask_node_ptr = root->get_children()[i].get(); - EXPECT_EQ(mask_node_ptr->get_type(), render_node_type::mask); - - auto* mask = static_cast(mask_node_ptr); - ASSERT_EQ(mask->get_child_count(), 1); - EXPECT_EQ(mask->get_children()[0]->get_type(), render_node_type::geometry); - } -} - -// ============================================================================ -// 测试 7: 复杂混合场景 -// ============================================================================ - -/// @brief 验证复杂的混合场景 -/// 包含多个独立的 mask、post_effect 以及它们的嵌套组合 -TEST(RenderTreeIntegrationTest, ComplexMixedScenario) { - render_command_builder builder; - - // 根级别的几何 - builder.draw_rectangle({0, 0}, {50, 50}, color::white()); - - // 第一个 mask 区域 - mask_params mask1; - mask1.type = mask_type::rect; - - builder.begin_mask(mask1, aabb2d_t(vec2f_t(100.0f, 0.0f), vec2f_t(200.0f, 100.0f))); - builder.draw_rectangle({110, 10}, {80, 80}, color::red()); - builder.draw_blur({110, 10}, {80, 80}, 5.0f); - builder.end_mask(); - - // 根级别的后效 - builder.draw_vignette({250, 0}, {100, 100}, 0.5f, 0.3f); - - // 第二个 mask 区域(包含嵌套后效和遮罩) - mask_params mask2; - mask2.type = mask_type::circle; - - builder.begin_mask(mask2, aabb2d_t(vec2f_t(400.0f, 0.0f), vec2f_t(600.0f, 200.0f))); - builder.draw_rectangle({420, 20}, {150, 150}, color::green()); - - // 嵌套后效 - builder.draw_chromatic_aberration({420, 20}, {150, 150}, 2.0f); - - // 嵌套遮罩 - mask_params nested_mask; - nested_mask.type = mask_type::rounded_rect; - nested_mask.corner_radii = rect_corner_radius(10.0f); - - builder.begin_mask(nested_mask, aabb2d_t(vec2f_t(450.0f, 50.0f), vec2f_t(550.0f, 150.0f))); - builder.draw_rectangle({460, 60}, {80, 80}, color::from_rgba(255, 255, 0)); // yellow - builder.end_mask(); - builder.end_mask(); - - // 根级别的更多几何 - builder.draw_rectangle({650, 0}, {50, 50}, color::blue()); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - ASSERT_EQ(root->get_child_count(), 5); // geo + mask + post_effect + mask + geo - - // 验证节点类型顺序 - EXPECT_EQ(root->get_children()[0]->get_type(), render_node_type::geometry); - EXPECT_EQ(root->get_children()[1]->get_type(), render_node_type::mask); - EXPECT_EQ(root->get_children()[2]->get_type(), render_node_type::post_effect); - EXPECT_EQ(root->get_children()[3]->get_type(), render_node_type::mask); - EXPECT_EQ(root->get_children()[4]->get_type(), render_node_type::geometry); - - // 验证第二个 mask 的内部结构 - auto* mask2_node = static_cast(root->get_children()[3].get()); - ASSERT_EQ(mask2_node->get_child_count(), 3); // geo + post_effect + nested_mask - - EXPECT_EQ(mask2_node->get_children()[0]->get_type(), render_node_type::geometry); - EXPECT_EQ(mask2_node->get_children()[1]->get_type(), render_node_type::post_effect); - EXPECT_EQ(mask2_node->get_children()[2]->get_type(), render_node_type::mask); - - // 验证嵌套遮罩 - auto* nested_mask_node = static_cast(mask2_node->get_children()[2].get()); - ASSERT_EQ(nested_mask_node->get_child_count(), 1); - EXPECT_EQ(nested_mask_node->get_children()[0]->get_type(), render_node_type::geometry); -} - -// ============================================================================ -// 测试 8: 空遮罩和空后效 -// ============================================================================ - -/// @brief 验证空遮罩和空后效的处理 -/// 创建不包含任何内容的 mask 和 post_effect,验证它们仍然被正确构建 -TEST(RenderTreeIntegrationTest, EmptyMaskAndPostEffect) { - render_command_builder builder; - - // 空遮罩 - mask_params empty_mask; - empty_mask.type = mask_type::rect; - - builder.begin_mask(empty_mask, aabb2d_t(vec2f_t(0.0f, 0.0f), vec2f_t(100.0f, 100.0f))); - builder.end_mask(); - - // 后效(没有子内容) - builder.draw_blur({150, 0}, {100, 100}, 10.0f); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - ASSERT_EQ(root->get_child_count(), 2); // empty_mask + post_effect - - // 验证空遮罩 - auto* mask_node_ptr = root->get_children()[0].get(); - EXPECT_EQ(mask_node_ptr->get_type(), render_node_type::mask); - - auto* mask = static_cast(mask_node_ptr); - EXPECT_EQ(mask->get_child_count(), 0); // 空遮罩没有子节点 - - // 验证后效 - auto* effect_node = root->get_children()[1].get(); - EXPECT_EQ(effect_node->get_type(), render_node_type::post_effect); - - auto* post_effect = static_cast(effect_node); - EXPECT_EQ(post_effect->get_child_count(), 0); // 后效没有子节点 -} - -// ============================================================================ -// 测试 9: 多种后效类型组合 -// ============================================================================ - -/// @brief 验证多种不同类型的后效可以正确组合 -TEST(RenderTreeIntegrationTest, MultiplePostEffectTypes) { - render_command_builder builder; - - // 各种不同类型的后效 - builder.draw_blur({0, 0}, {100, 100}, 10.0f); - builder.draw_vignette({110, 0}, {100, 100}, 0.5f, 0.4f); - builder.draw_chromatic_aberration({220, 0}, {100, 100}, 3.0f); - builder.draw_noise({330, 0}, {100, 100}, 0.2f, 1.5f); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - ASSERT_EQ(root->get_child_count(), 4); // 四种不同的后效 - - // 验证所有子节点都是 post_effect - for (size_t i = 0; i < 4; ++i) { - EXPECT_EQ(root->get_children()[i]->get_type(), render_node_type::post_effect); - } -} - -// ============================================================================ -// 测试 10: Z-Order 排序验证 -// ============================================================================ - -/// @brief 验证 z-order 对节点顺序的影响 -TEST(RenderTreeIntegrationTest, ZOrderSorting) { - render_command_builder builder; - - // 使用不同的 z-order 和 mask 来创建独立的节点 - mask_params params1; - params1.type = mask_type::rect; - - // z-order = 2 的 mask - builder.set_z_order(2); - builder.begin_mask(params1, aabb2d_t(vec2f_t(0.0f, 0.0f), vec2f_t(50.0f, 50.0f))); - builder.draw_rectangle({0, 0}, {50, 50}, color::red()); - builder.end_mask(); - - // z-order = 0 的 mask - mask_params params2; - params2.type = mask_type::circle; - builder.set_z_order(0); - builder.begin_mask(params2, aabb2d_t(vec2f_t(60.0f, 0.0f), vec2f_t(110.0f, 50.0f))); - builder.draw_rectangle({60, 0}, {50, 50}, color::green()); - builder.end_mask(); - - // z-order = 1 的 mask - mask_params params3; - params3.type = mask_type::rounded_rect; - params3.corner_radii = rect_corner_radius(5.0f); - builder.set_z_order(1); - builder.begin_mask(params3, aabb2d_t(vec2f_t(120.0f, 0.0f), vec2f_t(170.0f, 50.0f))); - builder.draw_rectangle({120, 0}, {50, 50}, color::blue()); - builder.end_mask(); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - ASSERT_EQ(root->get_child_count(), 3); // 三个独立的 mask - - // 验证节点类型和 z-order 顺序:mask(0), mask(1), mask(2) - EXPECT_EQ(root->get_children()[0]->get_type(), render_node_type::mask); - EXPECT_EQ(root->get_children()[0]->get_z_order(), 0); - - EXPECT_EQ(root->get_children()[1]->get_type(), render_node_type::mask); - EXPECT_EQ(root->get_children()[1]->get_z_order(), 1); - - EXPECT_EQ(root->get_children()[2]->get_type(), render_node_type::mask); - EXPECT_EQ(root->get_children()[2]->get_z_order(), 2); -} - -// ============================================================================ -// Widget 系统测试 - 需要包含 widget 头文件 -// ============================================================================ - -#include "button.h" -#include "imager.h" -#include "layout/overlay.h" -#include "layout/stack.h" -#include "post_effect.h" -#include "mask/rounded_rect_mask.h" -#include "mask/circle_mask.h" -#include "mask/mask_widget.h" -#include "layout/modifiers/modifiers.h" -#include "../../src/widget/layout_state.h" - -// ============================================================================ -// 测试 11: Widget 代码生成 - 基本 button -// ============================================================================ - -/// @brief 验证 button widget 能否正确生成渲染命令 -TEST(RenderTreeIntegrationTest, WidgetCodeGeneration_Button) { - render_command_builder builder; - layout_state state; - - // 设置布局状态 - state.set_root(transform2d_t::Identity(), vec2f_t(800.0f, 600.0f)); - - // 创建 button widget - auto on_click = []() { /* do nothing */ }; - button btn{"Test Button", on_click}; - - // 布局和生成命令 - btn.arrange(state); - btn.build_render_commands(builder); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - button 应该生成至少一个几何节点 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - EXPECT_GT(root->get_child_count(), 0); -} - -// ============================================================================ -// 测试 12: Widget 代码生成 - imager with rounded_rect_mask -// ============================================================================ - -/// @brief 验证 imager | mask(rounded_rect_mask) 的命令生成 -TEST(RenderTreeIntegrationTest, WidgetCodeGeneration_ImagerWithRoundedMask) { - render_command_builder builder; - layout_state state; - - state.set_root(transform2d_t::Identity(), vec2f_t(800.0f, 600.0f)); - - // 创建:imager | mask(rounded_rect_mask{}.corner_radius(30.f)) - auto widget = imager{} - .texture_id(1) // 假设纹理 ID 为 1 - .set_texture_size(vec2i_t(400, 300)) - | mask(rounded_rect_mask{}.corner_radius(30.f)); - - // 布局和生成命令 - widget.arrange(state); - widget.build_render_commands(builder); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - - // 应该有一个 mask 节点 - ASSERT_GT(root->get_child_count(), 0); - - // 查找 mask 节点 - bool found_mask = false; - for (size_t i = 0; i < root->get_child_count(); ++i) { - if (root->get_children()[i]->get_type() == render_node_type::mask) { - auto* mask_node_ptr = static_cast(root->get_children()[i].get()); - EXPECT_EQ(mask_node_ptr->get_mask_params().type, mask_type::rounded_rect); - EXPECT_FLOAT_EQ(mask_node_ptr->get_mask_params().corner_radii.top_left, 30.0f); - found_mask = true; - break; - } - } - EXPECT_TRUE(found_mask) << "应该找到 rounded_rect mask 节点"; -} - -// ============================================================================ -// 测试 13: Widget 代码生成 - overlay with post_effect -// ============================================================================ - -/// @brief 验证 overlay 中包含 imager 和 post_effect_widget 的命令生成 -TEST(RenderTreeIntegrationTest, WidgetCodeGeneration_OverlayWithPostEffect) { - render_command_builder builder; - layout_state state; - - state.set_root(transform2d_t::Identity(), vec2f_t(800.0f, 600.0f)); - - // 创建:overlay { imager, post_effect_widget } - overlay ovl{ - imager{} - .texture_id(1) - .set_texture_size(vec2i_t(400, 300)), - post_effect_widget{ - blur_effect{20.0f} - } - }; - - // 布局和生成命令 - ovl.arrange(state); - ovl.build_render_commands(builder); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - EXPECT_GT(root->get_child_count(), 0); - - // 应该包含 geometry 和 post_effect 节点 - bool found_geometry = false; - bool found_post_effect = false; - - for (size_t i = 0; i < root->get_child_count(); ++i) { - auto type = root->get_children()[i]->get_type(); - if (type == render_node_type::geometry) found_geometry = true; - if (type == render_node_type::post_effect) found_post_effect = true; - } - - EXPECT_TRUE(found_geometry) << "应该找到 geometry 节点"; - EXPECT_TRUE(found_post_effect) << "应该找到 post_effect 节点"; -} - -// ============================================================================ -// 测试 14: Widget 代码生成 - v_stack 布局 -// ============================================================================ - -/// @brief 验证 v_stack 中包含多个 widget 的命令生成 -TEST(RenderTreeIntegrationTest, WidgetCodeGeneration_VStack) { - render_command_builder builder; - layout_state state; - - state.set_root(transform2d_t::Identity(), vec2f_t(800.0f, 600.0f)); - - auto on_click = []() { /* do nothing */ }; - - // 创建 v_stack - v_stack stack{ - button{"Button 1", on_click}, - button{"Button 2", on_click}, - button{"Button 3", on_click} - }; - - // 布局和生成命令 - stack.arrange(state); - stack.build_render_commands(builder); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - - // v_stack 中的 3 个 button 应该生成多个节点 - EXPECT_GT(root->get_child_count(), 0); -} - -// ============================================================================ -// 测试 15: Widget 代码生成 - 完整场景(模拟 create_widget_test_commands) -// ============================================================================ - -/// @brief 验证完整的 widget 场景能否正确生成渲染命令 -/// 模拟 create_widget_test_commands 中的复杂布局,并验证结构和顺序 -TEST(RenderTreeIntegrationTest, WidgetCodeGeneration_CompleteScenario) { - render_command_builder builder; - layout_state state; - - state.set_root(transform2d_t::Identity(), vec2f_t(800.0f, 600.0f)); - - auto on_click = []() { /* do nothing */ }; - - // 创建完整的 v_stack 布局 - v_stack stack{ - // 1. Button - button{"Hello", on_click}, - - // 2. 第一个 overlay: imager | mask(rounded_rect) | align | padding + post_effect - overlay{ - imager{} - .texture_id(1) - .set_texture_size(vec2i_t(400, 300)) - | mask(rounded_rect_mask{}.corner_radius(30.f)) - | align(alignment::CENTER) - | padding(10.f), - post_effect_widget{ - blur_effect{20.f} - } - } | stretch(), - - // 3. 第二个 overlay: imager | align | padding + post_effect,外层 | mask(circle) - overlay{ - imager{} - .texture_id(1) - .set_texture_size(vec2i_t(400, 300)) - | align(alignment::CENTER) - | padding(10.f), - post_effect_widget{ - blur_effect{20.f} - } - } | stretch() | mask(circle_mask{}) - }; - - // 布局和生成命令 - stack.arrange(state); - stack.build_render_commands(builder); - - // 构建渲染树 - render_tree_builder tree_builder; - auto tree = tree_builder.build(builder.get_commands()); - - // 验证树结构 - auto* root = static_cast(tree.get_root()); - ASSERT_NE(root, nullptr); - - // ======================================================================== - // 验证顶层结构和顺序 - // ======================================================================== - // 预期结构: - // root (group) - // ├─ button 的 geometry 节点(可能多个) - // ├─ 第一个 overlay 的 mask(rounded_rect) 节点 - // └─ 第二个 overlay 的 mask(circle) 节点 - - ASSERT_GT(root->get_child_count(), 0) << "根节点应该有子节点"; - - // 查找并验证 mask 节点 - std::vector mask_nodes; - for (size_t i = 0; i < root->get_child_count(); ++i) { - if (root->get_children()[i]->get_type() == render_node_type::mask) { - mask_nodes.push_back(static_cast(root->get_children()[i].get())); - } - } - - // 应该有至少 2 个 mask 节点 - ASSERT_GE(mask_nodes.size(), 2) << "应该至少有 2 个 mask 节点(rounded_rect 和 circle)"; - - // ======================================================================== - // 验证第一个 mask 节点(rounded_rect_mask) - // ======================================================================== - // 查找 rounded_rect mask - mask_node* rounded_rect_mask_node = nullptr; - for (auto* mask : mask_nodes) { - if (mask->get_mask_params().type == mask_type::rounded_rect) { - rounded_rect_mask_node = mask; - break; - } - } - - ASSERT_NE(rounded_rect_mask_node, nullptr) << "应该找到 rounded_rect mask 节点"; - EXPECT_FLOAT_EQ(rounded_rect_mask_node->get_mask_params().corner_radii.top_left, 30.0f) - << "rounded_rect mask 的圆角半径应该是 30.0f"; - - // 验证 rounded_rect mask 内部结构 - // 预期:imager(geometry),post_effect 在 overlay 层级,不在 mask 内部 - ASSERT_GE(rounded_rect_mask_node->get_child_count(), 1) - << "rounded_rect mask 应该至少有 1 个子节点(geometry)"; - - // 验证子节点包含 geometry - bool found_geometry = false; - - for (size_t i = 0; i < rounded_rect_mask_node->get_child_count(); ++i) { - auto type = rounded_rect_mask_node->get_children()[i]->get_type(); - if (type == render_node_type::geometry) { - found_geometry = true; - } - } - - EXPECT_TRUE(found_geometry) << "rounded_rect mask 内应该有 geometry 节点"; - - // ======================================================================== - // 验证第二个 mask 节点(circle_mask) - // ======================================================================== - // 查找 circle mask - mask_node* circle_mask_node = nullptr; - for (auto* mask : mask_nodes) { - if (mask->get_mask_params().type == mask_type::circle) { - circle_mask_node = mask; - break; - } - } - - ASSERT_NE(circle_mask_node, nullptr) << "应该找到 circle mask 节点"; - - // 验证 circle mask 内部结构 - // 预期:imager(geometry),post_effect 在 overlay 层级,不在 mask 内部 - ASSERT_GE(circle_mask_node->get_child_count(), 1) - << "circle mask 应该至少有 1 个子节点(geometry)"; - - // 验证子节点包含 geometry - found_geometry = false; - - for (size_t i = 0; i < circle_mask_node->get_child_count(); ++i) { - auto type = circle_mask_node->get_children()[i]->get_type(); - if (type == render_node_type::geometry) { - found_geometry = true; - } - } - - EXPECT_TRUE(found_geometry) << "circle mask 内应该有 geometry 节点"; - - // ======================================================================== - // 验证整体节点统计 - // ======================================================================== - int total_mask_count = 0; - int total_geometry_count = 0; - int total_post_effect_count = 0; - - std::function count_nodes = [&](render_node* node) { - if (!node) return; - - switch (node->get_type()) { - case render_node_type::mask: - total_mask_count++; - { - auto* mask_node_ptr = static_cast(node); - for (auto& child : mask_node_ptr->get_children()) { - count_nodes(child.get()); - } - } - break; - case render_node_type::geometry: - total_geometry_count++; - break; - case render_node_type::post_effect: - total_post_effect_count++; - break; - case render_node_type::group: - { - auto* group = static_cast(node); - for (auto& child : group->get_children()) { - count_nodes(child.get()); - } - } - break; - } - }; - - count_nodes(root); - - // 验证总数 - EXPECT_GE(total_mask_count, 2) << "总共应该至少有 2 个 mask 节点"; - EXPECT_GE(total_geometry_count, 3) << "总共应该至少有 3 个 geometry 节点(button + 2个imager)"; - EXPECT_GE(total_post_effect_count, 2) << "总共应该至少有 2 个 post_effect 节点(2个blur)"; -} \ No newline at end of file diff --git a/tools/templates/buffer_helpers/create_buffer.jinja2 b/tools/templates/buffer_helpers/create_buffer.jinja2 index 6a4cce3..6d51418 100644 --- a/tools/templates/buffer_helpers/create_buffer.jinja2 +++ b/tools/templates/buffer_helpers/create_buffer.jinja2 @@ -3,7 +3,7 @@ inline auto create_{{ buffer.variable_name }}_buffer( resource_manager& rm, size_t element_count -) -> expected_t +) -> expected_t { return rm.create_buffer( element_count * sizeof({{ buffer.name }}), diff --git a/tools/templates/buffer_manager/manager_class.jinja2 b/tools/templates/buffer_manager/manager_class.jinja2 index 2430cee..f6d19d0 100644 --- a/tools/templates/buffer_manager/manager_class.jinja2 +++ b/tools/templates/buffer_manager/manager_class.jinja2 @@ -13,7 +13,7 @@ public: vk::Device device, resource_manager& rm, size_t element_count - ) -> expected_t<{{ class_name }}> + ) -> expected_t<{{ class_name }}, vk::Result> { {%- for buffer in buffers %} auto {{ buffer.variable_name }}_result = create_{{ buffer.variable_name }}_typed(device, rm, element_count); diff --git a/tools/templates/typed_buffer/alias_and_factory.jinja2 b/tools/templates/typed_buffer/alias_and_factory.jinja2 index b7cd5fe..d7ab019 100644 --- a/tools/templates/typed_buffer/alias_and_factory.jinja2 +++ b/tools/templates/typed_buffer/alias_and_factory.jinja2 @@ -7,7 +7,7 @@ inline auto create_{{ buffer.variable_name }}_typed( vk::Device device, resource_manager& rm, size_t count -) -> expected_t<{{ buffer.variable_name }}_buffer> +) -> expected_t<{{ buffer.variable_name }}_buffer, vk::Result> { auto buffer_result = create_{{ buffer.variable_name }}_buffer(rm, count); if (!buffer_result) {