diff --git a/example/test_thread/main.cpp b/example/test_thread/main.cpp index 577b572..6ecd7a4 100644 --- a/example/test_thread/main.cpp +++ b/example/test_thread/main.cpp @@ -4,6 +4,7 @@ #include "ui/widgets/containers/overlay.h" #include "ui/widgets/containers/scroll_box/scroll_box.h" #include "ui/window/render_window.h" +#include "ui/tooltip/tooltip_types.h" #include "render/image/texture_manager.h" #include "render/text/font_manager.h" #include "ui/widgets/imager.h" @@ -27,7 +28,6 @@ int main(int argc, char* argv[]) { window_config.title = "Test Thread"; window_config.width = 800; window_config.height = 600; - window_config.enable_multithreading = true; window_config.vsync = true; if (!app.initialize(config, window_config)) { @@ -48,6 +48,11 @@ int main(int argc, char* argv[]) { throw std::runtime_error("加载字体失败"); const auto font_id = font_result.value(); + // 设置 tooltip 默认字体 + tooltip_config tooltip_cfg; + tooltip_cfg.default_font_id = font_id; + app.get_main_window()->set_tooltip_config(tooltip_cfg); + const auto root_widget = new_widget()[ new_widget()->font_id(font_id).text("你好,我们在此相遇").font_size(24).tooltip("测试tooltip"), new_widget()->font_id(font_id).font_size(24), diff --git a/src/core/application.cpp b/src/core/application.cpp index 358db5c..96000ee 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -120,15 +120,14 @@ namespace mirage { // ======================================================================== render_window_config rw_config{ - .title = window_config.title, - .width = window_config.width, - .height = window_config.height, - .resizable = window_config.resizable, - .vsync = window_config.vsync, - .enable_multithreading = window_config.enable_multithreading, - .triple_buffering = window_config.triple_buffering, - .adaptive_sync = config.adaptive_sync, - .target_fps = window_config.target_fps + .title = window_config.title, + .width = window_config.width, + .height = window_config.height, + .resizable = window_config.resizable, + .vsync = window_config.vsync, + .triple_buffering = window_config.triple_buffering, + .adaptive_sync = config.adaptive_sync, + .target_fps = window_config.target_fps }; auto rw_result = window_manager_->create_render_window(rw_config); @@ -261,6 +260,12 @@ namespace mirage { float delta_time = elapsed.count(); last_frame_time_ = current_time; + // 更新上下文提供者(处理异步字形生成等) + // 必须在窗口更新之前调用,这样所有窗口都能看到新生成的字形 + if (context_provider_) { + context_provider_->tick(); + } + // 由窗口管理器统一遍历所有窗口 window_manager_->tick(delta_time); diff --git a/src/core/application.h b/src/core/application.h index d3af46e..e345495 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -34,14 +34,13 @@ namespace mirage { /// @brief 主窗口配置 struct main_window_config { - std::string title = "Mirage Application"; ///< 窗口标题 - uint32_t width = 1280; ///< 窗口宽度 - uint32_t height = 720; ///< 窗口高度 - bool resizable = true; ///< 是否可调整大小 - bool vsync = true; ///< 是否启用垂直同步 - bool enable_multithreading = true; ///< 是否启用多线程渲染 - std::optional target_fps; ///< 目标帧率 - bool triple_buffering = true; ///< 是否启用三缓冲 + std::string title = "Mirage Application"; ///< 窗口标题 + uint32_t width = 1280; ///< 窗口宽度 + uint32_t height = 720; ///< 窗口高度 + bool resizable = true; ///< 是否可调整大小 + bool vsync = true; ///< 是否启用垂直同步 + std::optional target_fps; ///< 目标帧率 + bool triple_buffering = true; ///< 是否启用三缓冲 }; // ============================================================================ diff --git a/src/core/vulkan_context_provider.cpp b/src/core/vulkan_context_provider.cpp index d9ec0d9..678ece6 100644 --- a/src/core/vulkan_context_provider.cpp +++ b/src/core/vulkan_context_provider.cpp @@ -196,6 +196,18 @@ namespace mirage { return render_context_ ? render_context_->get_instance() : vk::Instance{}; } + void vulkan_context_provider::tick() { + if (!initialized_) { + return; + } + + // 处理异步字形生成 + // 这会将已完成的字形添加到缓存中,并递增版本号 + if (glyph_cache_) { + glyph_cache_->process_completed_glyphs(); + } + } + void vulkan_context_provider::wait_idle() { if (logical_device_) { auto result = logical_device_->get_handle().waitIdle(); diff --git a/src/core/vulkan_context_provider.h b/src/core/vulkan_context_provider.h index 75987aa..67f23f4 100644 --- a/src/core/vulkan_context_provider.h +++ b/src/core/vulkan_context_provider.h @@ -99,6 +99,11 @@ namespace mirage { [[nodiscard]] render_pipeline* get_render_pipeline() const { return render_pipeline_.get(); } + /// @brief 每帧更新(处理异步字形生成等) + /// + /// 应在主循环中每帧调用一次,在所有窗口更新之前。 + void tick(); + /// @brief 等待 GPU 空闲 void wait_idle(); diff --git a/src/render/pipeline/post_effect_applicator.cpp b/src/render/pipeline/post_effect_applicator.cpp index cf14364..a620cae 100644 --- a/src/render/pipeline/post_effect_applicator.cpp +++ b/src/render/pipeline/post_effect_applicator.cpp @@ -41,10 +41,10 @@ namespace mirage { } // 创建 Vulkan 资源 + // 描述符池由 window_render_context 管理,不再在这里创建 create_descriptor_set_layouts(); create_pipeline_layout(); create_sampler(); - create_descriptor_pool(frames_in_flight); create_param_buffers(); // 创建 Pipeline 管理器并初始化 @@ -152,63 +152,12 @@ namespace mirage { sampler_ = res_mgr_.get_sampler_cache().linear_clamp(); } - // ============================================================================ - // Descriptor Pool - // ============================================================================ - - void post_effect_applicator::create_descriptor_pool(uint32_t frames_in_flight) { - auto device = device_.get_handle(); - - // 为每帧创建独立的描述符池 - descriptor_pools_.resize(frames_in_flight); - - // 为每种后效预留足够的 descriptor sets(降低以节省内存) - std::array pool_sizes = { - { - {vk::DescriptorType::eCombinedImageSampler, 32}, // 纹理 - {vk::DescriptorType::eUniformBuffer, 32} // 参数 - } - }; - - vk::DescriptorPoolCreateInfo pool_info{ - vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet | - vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind, // 添加重置标志 - 64, // maxSets(降低以节省内存) - static_cast(pool_sizes.size()), - pool_sizes.data() - }; - - for (uint32_t i = 0; i < frames_in_flight; ++i) { - auto [result, pool] = device.createDescriptorPool(pool_info); - if (result != vk::Result::eSuccess) { - throw std::runtime_error("Failed to create descriptor pool for frame " + std::to_string(i)); - } - descriptor_pools_[i] = pool; - } - } - - void post_effect_applicator::begin_frame(uint32_t frame_index) { + void post_effect_applicator::begin_frame(uint32_t frame_index, vk::DescriptorPool external_pool) { // 保存当前帧索引 current_frame_index_ = frame_index; - // 重置当前帧的描述符池 - // - // 同步保证: - // 此函数在 render_pipeline::render_frame() 中被调用,调用时机在 - // frame_scheduler::begin_frame() 之后(该函数已等待此帧的fence,见 - // frame_scheduler.cpp:206-213)。这确保了上一轮该帧索引的GPU工作已完成, - // 因此重置描述符池是安全的。 - // - // 调用流程: - // 1. frame_scheduler::begin_frame() - 等待fence[frame_index](line 206) - // 2. post_effect_applicator::begin_frame(frame_index) - 安全重置池 ← 当前位置 - // 3. 渲染命令录制和提交 - // 4. frame_scheduler::end_frame() - signal fence[frame_index] - // - auto device = device_.get_handle(); - if (frame_index < descriptor_pools_.size()) { - (void)device.resetDescriptorPool(descriptor_pools_[frame_index]); - } + // 保存外部池引用(由 window_render_context 管理和重置) + current_external_pool_ = external_pool; } void post_effect_applicator::resize(uint32_t width, uint32_t height) { @@ -1982,19 +1931,17 @@ namespace mirage { ) -> std::pair { auto device = device_.get_handle(); - // 使用当前帧的描述符池 - if (current_frame_index_ >= descriptor_pools_.size()) { - throw std::runtime_error("Invalid frame index for descriptor pool"); - } + // 使用外部池(由 window_render_context 管理) + vk::DescriptorPool pool_to_use = current_external_pool_; // 分配 descriptor sets std::array layouts = { texture_descriptor_layout_, params_descriptor_layout_ }; - + vk::DescriptorSetAllocateInfo alloc_info{ - descriptor_pools_[current_frame_index_], + pool_to_use, static_cast(layouts.size()), layouts.data() }; @@ -2157,13 +2104,7 @@ namespace mirage { params_descriptor_layout_ = nullptr; } - // 清理所有描述符池 - for (auto& pool : descriptor_pools_) { - if (pool) { - device.destroyDescriptorPool(pool); - } - } - descriptor_pools_.clear(); + // 描述符池由 window_render_context 管理,不需要在这里清理 // 参数缓冲区使用 typed_buffer_owned RAII 管理,自动释放 blur_params_buffer_.reset(); diff --git a/src/render/pipeline/post_effect_applicator.h b/src/render/pipeline/post_effect_applicator.h index be71691..c030784 100644 --- a/src/render/pipeline/post_effect_applicator.h +++ b/src/render/pipeline/post_effect_applicator.h @@ -77,9 +77,14 @@ namespace mirage { /// 初始化 Pipeline 和临时资源 void initialize(uint32_t max_width, uint32_t max_height, uint32_t frames_in_flight = 3); - + // 注入统一目标池 void set_target_pool(unified_target_pool* pool) { target_pool_ = pool; } + + /// 每帧开始时调用 + /// @param frame_index 帧索引 + /// @param external_pool 外部描述符池(必须提供,由 window_render_context 管理) + void begin_frame(uint32_t frame_index, vk::DescriptorPool external_pool); // ======================================================================== // 类型安全接口 - 使用 frame_context @@ -264,8 +269,6 @@ namespace mirage { apply_impl(cmd, target, effect, time, source); } - /// 每帧开始时调用,重置描述符池 - void begin_frame(uint32_t frame_index); /// 窗口大小变化时调用 void resize(uint32_t width, uint32_t height); @@ -287,11 +290,10 @@ namespace mirage { std::unique_ptr pipeline_manager_; // Pipeline 资源 - vk::PipelineLayout pipeline_layout_; - vk::DescriptorSetLayout texture_descriptor_layout_; // set 0: 纹理 - vk::DescriptorSetLayout params_descriptor_layout_; // set 1: 参数 - std::vector descriptor_pools_; // 每帧独立的描述符池 - vk::Sampler sampler_; + vk::PipelineLayout pipeline_layout_; + vk::DescriptorSetLayout texture_descriptor_layout_; // set 0: 纹理 + vk::DescriptorSetLayout params_descriptor_layout_; // set 1: 参数 + vk::Sampler sampler_; // 参数缓冲区(用于各种后效)- 使用 RAII 管理 typed_buffer_owned blur_params_buffer_; @@ -307,12 +309,14 @@ namespace mirage { // 当前帧索引(用于选择正确的描述符池) uint32_t current_frame_index_ = 0; + + // 当前使用的外部描述符池(如果有) + vk::DescriptorPool current_external_pool_{nullptr}; // 内部方法 void create_descriptor_set_layouts(); void create_pipeline_layout(); void create_sampler(); - void create_descriptor_pool(uint32_t frames_in_flight); void create_param_buffers(); void cleanup(); diff --git a/src/render/pipeline/render_pipeline.cpp b/src/render/pipeline/render_pipeline.cpp index b79574f..9d7e158 100644 --- a/src/render/pipeline/render_pipeline.cpp +++ b/src/render/pipeline/render_pipeline.cpp @@ -82,10 +82,11 @@ namespace mirage { // ============================================================================ auto render_pipeline::register_window( - window_id id, - vk::SurfaceKHR surface, - uint32_t width, - uint32_t height + window_id id, + vk::SurfaceKHR surface, + uint32_t width, + uint32_t height, + render_window_type type ) -> bool { if (!initialized_) { throw std::runtime_error("render_pipeline not initialized"); @@ -115,11 +116,14 @@ namespace mirage { // 更新 blit 描述符 context->update_blit_descriptors(shared_resources_->get_blit_sampler()); - // 如果是第一个窗口,设置为主窗口 - if (window_contexts_.empty()) { + // 如果是第一个主窗口类型的窗口,设置为主窗口 + if (type == render_window_type::primary && main_window_id_ == 0) { main_window_id_ = id; } + // 存储窗口类型 + window_types_[id] = type; + window_contexts_[id] = std::move(context); return true; } @@ -136,13 +140,18 @@ namespace mirage { // 删除窗口上下文 window_contexts_.erase(it); - // 如果删除的是主窗口,重新选择一个 + // 删除窗口类型记录 + window_types_.erase(id); + + // 如果删除的是主窗口,重新选择一个主窗口类型的窗口 if (id == main_window_id_) { - if (!window_contexts_.empty()) { - main_window_id_ = window_contexts_.begin()->first; - } - else { - main_window_id_ = 0; + main_window_id_ = 0; + // 查找第一个主窗口类型的窗口 + for (const auto& [win_id, win_type] : window_types_) { + if (win_type == render_window_type::primary) { + main_window_id_ = win_id; + break; + } } } } @@ -170,6 +179,26 @@ namespace mirage { } } + auto render_pipeline::get_primary_window() const -> std::optional { + if (main_window_id_ != 0 && window_contexts_.contains(main_window_id_)) { + return main_window_id_; + } + return std::nullopt; + } + + auto render_pipeline::get_render_window_type(window_id id) const -> std::optional { + auto it = window_types_.find(id); + if (it != window_types_.end()) { + return it->second; + } + return std::nullopt; + } + + auto render_pipeline::is_primary_window(window_id id) const -> bool { + auto type = get_render_window_type(id); + return type.has_value() && type.value() == render_window_type::primary; + } + auto render_pipeline::get_window_context(window_id id) -> window_render_context* { auto it = window_contexts_.find(id); return it != window_contexts_.end() ? it->second.get() : nullptr; @@ -180,6 +209,29 @@ namespace mirage { return it != window_contexts_.end() ? it->second.get() : nullptr; } + // ============================================================================ + // 上下文切换 + // ============================================================================ + + void render_pipeline::begin_window_context(window_id id) { + if (active_window_context_.has_value() && *active_window_context_ != id) { + // 如果有不同的活动上下文,先结束它 + end_window_context(*active_window_context_); + } + active_window_context_ = id; + + // 重置渲染器状态,确保窗口切换时不会有状态污染 + if (shared_resources_) { + shared_resources_->reset_renderer_state(); + } + } + + void render_pipeline::end_window_context(window_id id) { + if (active_window_context_.has_value() && *active_window_context_ == id) { + active_window_context_.reset(); + } + } + // ============================================================================ // 渲染接口 // ============================================================================ @@ -287,25 +339,36 @@ namespace mirage { throw std::runtime_error("render_pipeline not initialized"); } + // 获取窗口ID并激活上下文 + window_id id = window.get_window_id(); + begin_window_context(id); + + // 使用 RAII 确保在所有返回路径上都调用 end_window_context + struct context_guard { + render_pipeline* pipeline; + window_id id; + ~context_guard() { pipeline->end_window_context(id); } + } guard{this, id}; + // 获取帧索引 uint32_t frame_index = window.get_current_frame(); - // 1. 等待帧栅栏 - if (!shared_resources_->wait_for_frame(frame_index)) { + // 1. 等待帧栅栏(使用窗口独立的帧栅栏) + if (!window.wait_for_frame(frame_index)) { return false; } // 2. 获取 swapchain image auto maybe_image_index = window.acquire_next_image( - shared_resources_->get_frame_fence(frame_index) + window.get_frame_fence(frame_index) ); if (!maybe_image_index) { return false; // 需要重建 swapchain } uint32_t image_index = *maybe_image_index; - // 重置帧栅栏 - shared_resources_->reset_frame_fence(frame_index); + // 重置帧栅栏(使用窗口独立的帧栅栏) + window.reset_frame_fence(frame_index); // 3. 获取窗口尺寸和资源 auto extent = window.get_extent(); @@ -331,28 +394,37 @@ namespace mirage { } } - // 5. 开始命令缓冲 - auto cmd = shared_resources_->begin_command_buffer(frame_index); - - // 6. 重置后效描述符池 + // 5. 开始命令缓冲(使用窗口独立的命令缓冲) + auto cmd = window.begin_command_buffer(frame_index); + + // 6. 重置窗口的描述符池(解决多窗口同步问题) + window.reset_descriptor_pools(frame_index); + + // 7. 获取窗口的描述符池 + auto text_pool = window.get_text_descriptor_pool(frame_index); + auto image_pool = window.get_image_descriptor_pool(frame_index); + auto mask_pool = window.get_mask_descriptor_pool(frame_index); + auto post_effect_pool = window.get_post_effect_descriptor_pool(frame_index); + + // 8. 重置后效描述符池(使用窗口的描述符池) auto* effects = shared_resources_->get_post_effect_applicator(); - effects->begin_frame(frame_index); - - // 7. 开始各渲染器的帧 + effects->begin_frame(frame_index, post_effect_pool); + + // 9. 开始各渲染器的帧(使用窗口的描述符池) auto* geometry = shared_resources_->get_geometry_renderer(); auto* imager = shared_resources_->get_image_renderer(); auto* mask = shared_resources_->get_mask_renderer(); auto* text = shared_resources_->get_text_renderer(); - + geometry->begin_frame(frame_index, viewport_size); - imager->begin_frame(frame_index, viewport_size); - mask->begin_frame(frame_index, viewport_size); - text->begin_frame(frame_index, viewport_size); + imager->begin_frame(frame_index, viewport_size, image_pool); + mask->begin_frame(frame_index, viewport_size, mask_pool); + text->begin_frame(frame_index, viewport_size, text_pool); - // 8. 上传文本图集纹理 + // 10. 上传文本图集纹理 text->upload_textures(cmd); - - // 9. 后效预分析 + + // 11. 后效预分析 std::unordered_map effect_decisions; // 收集后效节点 @@ -401,7 +473,7 @@ namespace mirage { } } - // 10. 计算 scissor 区域 + // 12. 计算 scissor 区域 bool use_full_render = need_full_render; vk::Rect2D scissor_rect; @@ -449,7 +521,7 @@ namespace mirage { geometry->set_dirty_scissor(scissor_rect); } - // 11. 开始离屏渲染 Pass + // 13. 开始离屏渲染 Pass // 创建帧上下文 auto idle_ctx = make_frame_context( frame_resources{cmd, frame_index, &offscreen} @@ -474,7 +546,7 @@ namespace mirage { cmd.clearAttachments(1, &clear_attachment, 1, &clear_rect); } - // 12. 执行渲染树 + // 14. 执行渲染树 executor_context exec_ctx{ .geometry = geometry, .image = imager, @@ -489,7 +561,7 @@ namespace mirage { auto* tree_executor = shared_resources_->get_tree_executor(); tree_executor->execute(tree, exec_ctx); - // 13. 结束离屏渲染 Pass + // 15. 结束离屏渲染 Pass idle_ctx = std::move(offscreen_ctx).end_offscreen_pass(); // 更新状态 @@ -500,16 +572,16 @@ namespace mirage { geometry->clear_dirty_scissor(); } - // 14. 结束各渲染器的帧 + // 16. 结束各渲染器的帧 geometry->end_frame(); imager->end_frame(frame_index); mask->end_frame(); text->end_frame(frame_index); - // 15. 准备 Blit 到 Swapchain + // 17. 准备 Blit 到 Swapchain offscreen.transition_to(cmd, offscreen_target::state::shader_read); - // 16. 开始 Swapchain Pass + // 18. 开始 Swapchain Pass auto swapchain_ctx = std::move(idle_ctx).begin_swapchain_pass( window.get_swapchain_render_pass(use_full_render), window.get_framebuffer(image_index), @@ -517,20 +589,20 @@ namespace mirage { use_full_render ); - // 17. Blit 到 swapchain + // 19. Blit 到 swapchain std::optional blit_scissor; if (!use_full_render && dirty_rect.has_value()) { blit_scissor = scissor_rect; } blit_to_swapchain(swapchain_ctx, window, frame_index, blit_scissor); - // 18. 结束 Swapchain Pass + // 20. 结束 Swapchain Pass idle_ctx = std::move(swapchain_ctx).end_swapchain_pass(); - // 19. 结束命令缓冲 - shared_resources_->end_command_buffer(frame_index); + // 21. 结束命令缓冲(使用窗口独立的命令缓冲) + window.end_command_buffer(frame_index); - // 20. 提交命令缓冲 + // 22. 提交命令缓冲 vk::SubmitInfo submit_info{}; vk::Semaphore wait_semaphore = window.get_acquire_semaphore(frame_index); @@ -546,20 +618,21 @@ namespace mirage { submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &signal_semaphore; + // 使用窗口独立的帧栅栏 auto result = device_.get_graphics_queue().submit(1, &submit_info, - shared_resources_->get_frame_fence(frame_index)); + window.get_frame_fence(frame_index)); if (result != vk::Result::eSuccess) { return false; } - // 21. Present + // 23. Present auto present_result = window.present(device_.get_present_queue(), image_index); if (present_result == vk::Result::eErrorOutOfDateKHR || present_result == vk::Result::eSuboptimalKHR) { return false; // 需要重建 swapchain } - // 22. 推进帧索引 + // 24. 推进帧索引 window.advance_frame(); return true; diff --git a/src/render/pipeline/render_pipeline.h b/src/render/pipeline/render_pipeline.h index bf3bc99..956a810 100644 --- a/src/render/pipeline/render_pipeline.h +++ b/src/render/pipeline/render_pipeline.h @@ -14,6 +14,7 @@ #include #include #include +#include namespace mirage { class text_shaper; @@ -24,6 +25,16 @@ namespace mirage { } namespace mirage { + /// 渲染窗口类型枚举 + /// + /// 用于区分主窗口和辅助窗口(如 tooltip) + /// 注意:此枚举与 ui/window/window_type.h 中的 window_type 不同 + /// 这里的类型用于渲染管线内部区分窗口的渲染方式 + enum class render_window_type { + primary, ///< 主窗口 - 由渲染线程统一渲染 + auxiliary ///< 辅助窗口 - 独立渲染(如 tooltip) + }; + /// 渲染管线 - 整合所有组件的主入口(支持多窗口渲染) /// /// 职责: @@ -80,12 +91,14 @@ namespace mirage { /// @param surface 窗口表面 /// @param width 初始宽度 /// @param height 初始高度 + /// @param type 渲染窗口类型(默认为主窗口) /// @return 是否成功注册 [[nodiscard]] auto register_window( - window_id id, - vk::SurfaceKHR surface, - uint32_t width, - uint32_t height + window_id id, + vk::SurfaceKHR surface, + uint32_t width, + uint32_t height, + render_window_type type = render_window_type::primary ) -> bool; /// 注销窗口 @@ -109,6 +122,39 @@ namespace mirage { /// 获取主窗口 ID [[nodiscard]] auto get_main_window() const -> window_id { return main_window_id_; } + /// 获取主窗口 ID(可选版本) + /// @return 如果存在主窗口则返回其 ID,否则返回 std::nullopt + [[nodiscard]] auto get_primary_window() const -> std::optional; + + /// 获取渲染窗口类型 + /// @param id 窗口标识 + /// @return 渲染窗口类型,如果窗口不存在则返回 std::nullopt + [[nodiscard]] auto get_render_window_type(window_id id) const -> std::optional; + + /// 检查窗口是否为主窗口类型 + /// @param id 窗口标识 + [[nodiscard]] auto is_primary_window(window_id id) const -> bool; + + // ========================================================================= + // 上下文切换 + // ========================================================================= + + /** + * @brief 激活指定窗口的渲染上下文 + * @param id 窗口ID + * + * 在渲染窗口前调用,确保渲染器状态正确初始化 + */ + void begin_window_context(window_id id); + + /** + * @brief 释放指定窗口的渲染上下文 + * @param id 窗口ID + * + * 在渲染窗口后调用,清理渲染器状态 + */ + void end_window_context(window_id id); + // ========================================================================= // 渲染接口 // ========================================================================= @@ -235,12 +281,18 @@ namespace mirage { /// 窗口上下文映射 std::unordered_map> window_contexts_; + /// 渲染窗口类型映射 + std::unordered_map window_types_; + /// 主窗口 ID window_id main_window_id_{0}; /// 是否已初始化 bool initialized_ = false; + /// 当前活动的窗口上下文 + std::optional active_window_context_; + // ========================================================================= // 内部方法 // ========================================================================= diff --git a/src/render/pipeline/shared_render_resources.cpp b/src/render/pipeline/shared_render_resources.cpp index a68ba7f..a41d6c2 100644 --- a/src/render/pipeline/shared_render_resources.cpp +++ b/src/render/pipeline/shared_render_resources.cpp @@ -334,6 +334,20 @@ namespace mirage { return upload_scheduler_.get(); } + // ============================================================================ + // 状态管理 + // ============================================================================ + + void shared_render_resources::reset_renderer_state() { + // 重置各渲染器的内部状态 + // 注意:这里不需要重置GPU资源,只需要重置CPU端的状态追踪 + // 渲染器的 begin_frame() 会在每帧开始时正确初始化状态 + + // 如果渲染器有需要在窗口切换时重置的状态,在这里调用 + // 目前渲染器的设计是每帧通过 begin_frame() 重置状态, + // 所以这里主要是一个扩展点,用于未来可能需要的状态重置 + } + // ============================================================================ // 资源管理 // ============================================================================ diff --git a/src/render/pipeline/shared_render_resources.h b/src/render/pipeline/shared_render_resources.h index db8c2b5..7cf7258 100644 --- a/src/render/pipeline/shared_render_resources.h +++ b/src/render/pipeline/shared_render_resources.h @@ -200,6 +200,17 @@ namespace mirage { /// @brief 获取配置 [[nodiscard]] auto get_config() const noexcept -> const shared_resources_config& { return config_; } + // ===================================================================== + // 状态管理 + // ===================================================================== + + /** + * @brief 重置所有渲染器的内部状态 + * + * 在窗口上下文切换时调用,确保渲染器状态不会在窗口间污染 + */ + void reset_renderer_state(); + // ===================================================================== // 资源管理 // ===================================================================== diff --git a/src/render/pipeline/window_render_context.cpp b/src/render/pipeline/window_render_context.cpp index 9bb3243..a66f9d3 100644 --- a/src/render/pipeline/window_render_context.cpp +++ b/src/render/pipeline/window_render_context.cpp @@ -39,15 +39,23 @@ namespace mirage { , present_semaphores_(std::move(other.present_semaphores_)) , blit_desc_pool_(other.blit_desc_pool_) , blit_descriptor_sets_(std::move(other.blit_descriptor_sets_)) + , command_pool_(other.command_pool_) + , command_buffers_(std::move(other.command_buffers_)) + , frame_fences_(std::move(other.frame_fences_)) , image_initialized_(std::move(other.image_initialized_)) , image_consistent_(std::move(other.image_consistent_)) , frames_in_flight_(other.frames_in_flight_) , current_frame_(other.current_frame_) - , blit_desc_layout_(other.blit_desc_layout_) { + , blit_desc_layout_(other.blit_desc_layout_) + , text_descriptor_pools_(std::move(other.text_descriptor_pools_)) + , image_descriptor_pools_(std::move(other.image_descriptor_pools_)) + , mask_descriptor_pools_(std::move(other.mask_descriptor_pools_)) + , post_effect_descriptor_pools_(std::move(other.post_effect_descriptor_pools_)) { // 清空源对象的 Vulkan 句柄,防止双重销毁 other.surface_ = nullptr; other.physical_device_ = nullptr; other.blit_desc_pool_ = nullptr; + other.command_pool_ = nullptr; other.device_ = nullptr; other.res_mgr_ = nullptr; } @@ -70,16 +78,24 @@ namespace mirage { present_semaphores_ = std::move(other.present_semaphores_); blit_desc_pool_ = other.blit_desc_pool_; blit_descriptor_sets_ = std::move(other.blit_descriptor_sets_); - image_initialized_ = std::move(other.image_initialized_); - image_consistent_ = std::move(other.image_consistent_); - frames_in_flight_ = other.frames_in_flight_; - current_frame_ = other.current_frame_; - blit_desc_layout_ = other.blit_desc_layout_; - + command_pool_ = other.command_pool_; + command_buffers_ = std::move(other.command_buffers_); + frame_fences_ = std::move(other.frame_fences_); + image_initialized_ = std::move(other.image_initialized_); + image_consistent_ = std::move(other.image_consistent_); + frames_in_flight_ = other.frames_in_flight_; + current_frame_ = other.current_frame_; + blit_desc_layout_ = other.blit_desc_layout_; + text_descriptor_pools_ = std::move(other.text_descriptor_pools_); + image_descriptor_pools_ = std::move(other.image_descriptor_pools_); + mask_descriptor_pools_ = std::move(other.mask_descriptor_pools_); + post_effect_descriptor_pools_ = std::move(other.post_effect_descriptor_pools_); + // 清空源对象的 Vulkan 句柄 other.surface_ = nullptr; other.physical_device_ = nullptr; other.blit_desc_pool_ = nullptr; + other.command_pool_ = nullptr; other.device_ = nullptr; other.res_mgr_ = nullptr; } @@ -148,6 +164,16 @@ namespace mirage { create_sync_objects(); } + // 创建/重建命令资源(如果尚未创建) + if (command_buffers_.empty()) { + create_command_resources(); + } + + // 创建/重建帧栅栏(如果尚未创建) + if (frame_fences_.empty()) { + create_frame_fences(); + } + // 创建/重建离屏目标 if (offscreen_) { offscreen_->resize(width, height); @@ -168,10 +194,15 @@ namespace mirage { if (blit_descriptor_sets_.empty() && blit_desc_layout_) { create_blit_descriptors(blit_desc_layout_); } - + + // 创建渲染器描述符池(如果尚未创建) + if (text_descriptor_pools_.empty()) { + create_renderer_descriptor_pools(); + } + // 重置图像状态 reset_all_image_states(); - + return true; } @@ -483,6 +514,380 @@ namespace mirage { } } + // ============================================================================ + // 命令缓冲和帧同步方法实现 + // ============================================================================ + + auto window_render_context::get_command_buffer(uint32_t frame_index) const -> vk::CommandBuffer { + if (frame_index >= command_buffers_.size()) { + return nullptr; + } + return command_buffers_[frame_index]; + } + + auto window_render_context::begin_command_buffer(uint32_t frame_index) -> vk::CommandBuffer { + if (frame_index >= command_buffers_.size()) { + throw std::runtime_error("Invalid frame index for command buffer"); + } + + auto& cmd = command_buffers_[frame_index]; + + // 重置命令缓冲 + auto reset_result = cmd.reset(); + if (reset_result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to reset command buffer"); + } + + // 开始录制 + vk::CommandBufferBeginInfo begin_info{}; + auto begin_result = cmd.begin(begin_info); + if (begin_result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to begin command buffer"); + } + + return cmd; + } + + auto window_render_context::end_command_buffer(uint32_t frame_index) -> void { + if (frame_index >= command_buffers_.size()) { + throw std::runtime_error("Invalid frame index for command buffer"); + } + + auto& cmd = command_buffers_[frame_index]; + auto end_result = cmd.end(); + if (end_result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to end command buffer"); + } + } + + auto window_render_context::wait_for_frame(uint32_t frame_index) -> bool { + if (frame_index >= frame_fences_.size()) { + return false; + } + + auto& fence = frame_fences_[frame_index]; + uint32_t retry_count = 0; + vk::Result wait_result; + + do { + wait_result = device_->get_handle().waitForFences( + 1, &fence, + VK_TRUE, + fence_timeout_ns_ + ); + + if (wait_result == vk::Result::eSuccess) { + return true; + } + else if (wait_result == vk::Result::eTimeout) { + retry_count++; + std::cerr << "[WINDOW_RENDER_CONTEXT] 警告: Fence 等待超时 (窗口 " + << id_ << ", 帧 " << frame_index << ", 重试 " << retry_count + << "/" << max_fence_retries_ << ")" << std::endl; + + if (retry_count >= max_fence_retries_) { + std::cerr << "[WINDOW_RENDER_CONTEXT] 错误: Fence 等待超时次数过多" << std::endl; + return false; + } + } + else { + std::cerr << "[WINDOW_RENDER_CONTEXT] 错误: Fence 等待失败" << std::endl; + return false; + } + } + while (wait_result == vk::Result::eTimeout && retry_count < max_fence_retries_); + + return false; + } + + auto window_render_context::reset_frame_fence(uint32_t frame_index) -> void { + if (frame_index >= frame_fences_.size()) { + return; + } + + auto& fence = frame_fences_[frame_index]; + auto result = device_->get_handle().resetFences(1, &fence); + if (result != vk::Result::eSuccess) { + std::cerr << "[WINDOW_RENDER_CONTEXT] 警告: 重置 Fence 失败" << std::endl; + } + } + + auto window_render_context::get_frame_fence(uint32_t frame_index) const -> vk::Fence { + if (frame_index >= frame_fences_.size()) { + return nullptr; + } + return frame_fences_[frame_index]; + } + + auto window_render_context::create_command_resources() -> void { + if (!device_) { + return; + } + + // 创建命令池 + vk::CommandPoolCreateInfo pool_info{}; + pool_info.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + pool_info.queueFamilyIndex = device_->get_graphics_family_index(); + + auto [pool_result, pool] = device_->get_handle().createCommandPool(pool_info); + if (pool_result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create command pool for window"); + } + command_pool_ = pool; + + // 分配命令缓冲 + vk::CommandBufferAllocateInfo alloc_info{}; + alloc_info.commandPool = command_pool_; + alloc_info.level = vk::CommandBufferLevel::ePrimary; + alloc_info.commandBufferCount = frames_in_flight_; + + auto [alloc_result, buffers] = device_->get_handle().allocateCommandBuffers(alloc_info); + if (alloc_result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to allocate command buffers for window"); + } + command_buffers_ = buffers; + } + + auto window_render_context::create_frame_fences() -> void { + if (!device_) { + return; + } + + frame_fences_.resize(frames_in_flight_); + + vk::FenceCreateInfo fence_info{}; + fence_info.flags = vk::FenceCreateFlagBits::eSignaled; // 初始状态为已信号 + + for (uint32_t i = 0; i < frames_in_flight_; ++i) { + auto [result, fence] = device_->get_handle().createFence(fence_info); + if (result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create fence for window frame " + std::to_string(i)); + } + frame_fences_[i] = fence; + } + } + + // ============================================================================ + // 渲染器描述符池管理 + // ============================================================================ + + auto window_render_context::create_renderer_descriptor_pools() -> void { + if (!device_) { + return; + } + + auto device_handle = device_->get_handle(); + + // 为每帧创建独立的描述符池 + text_descriptor_pools_.resize(frames_in_flight_); + image_descriptor_pools_.resize(frames_in_flight_); + mask_descriptor_pools_.resize(frames_in_flight_); + post_effect_descriptor_pools_.resize(frames_in_flight_); + + // ========================================================================= + // 文本渲染器描述符池配置 + // 参考 text_renderer.cpp 中的配置: + // - MAX_ATLAS_PAGES = 16 个 CombinedImageSampler + // - 8 个 UniformBuffer + // - maxSets = 16 + // ========================================================================= + { + constexpr uint32_t MAX_ATLAS_PAGES = 16; + + std::array pool_sizes{}; + pool_sizes[0].type = vk::DescriptorType::eCombinedImageSampler; + pool_sizes[0].descriptorCount = MAX_ATLAS_PAGES * 2; + pool_sizes[1].type = vk::DescriptorType::eUniformBuffer; + pool_sizes[1].descriptorCount = 8; + + vk::DescriptorPoolCreateInfo pool_info{}; + pool_info.poolSizeCount = static_cast(pool_sizes.size()); + pool_info.pPoolSizes = pool_sizes.data(); + pool_info.maxSets = 16; + + for (uint32_t i = 0; i < frames_in_flight_; ++i) { + auto [pool_result, pool] = device_handle.createDescriptorPool(pool_info); + if (pool_result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create text descriptor pool for window frame " + std::to_string(i)); + } + text_descriptor_pools_[i] = pool; + } + } + + // ========================================================================= + // 图像渲染器描述符池配置 + // 参考 image_renderer.cpp 中的配置: + // - max_textures_per_frame = 32 个 CombinedImageSampler + // - maxSets = 32 + // ========================================================================= + { + vk::DescriptorPoolSize pool_size{}; + pool_size.type = vk::DescriptorType::eCombinedImageSampler; + pool_size.descriptorCount = 32; + + vk::DescriptorPoolCreateInfo pool_info{}; + pool_info.poolSizeCount = 1; + pool_info.pPoolSizes = &pool_size; + pool_info.maxSets = 32; + + for (uint32_t i = 0; i < frames_in_flight_; ++i) { + auto [pool_result, pool] = device_handle.createDescriptorPool(pool_info); + if (pool_result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create image descriptor pool for window frame " + std::to_string(i)); + } + image_descriptor_pools_[i] = pool; + } + } + + // ========================================================================= + // 遮罩渲染器描述符池配置 + // 参考 mask_renderer.cpp 中的配置: + // - max_masks = 16 个 CombinedImageSampler + // - maxSets = 16 + // ========================================================================= + { + vk::DescriptorPoolSize pool_size{}; + pool_size.type = vk::DescriptorType::eCombinedImageSampler; + pool_size.descriptorCount = 16; + + vk::DescriptorPoolCreateInfo pool_info{}; + pool_info.poolSizeCount = 1; + pool_info.pPoolSizes = &pool_size; + pool_info.maxSets = 16; + + for (uint32_t i = 0; i < frames_in_flight_; ++i) { + auto [pool_result, pool] = device_handle.createDescriptorPool(pool_info); + if (pool_result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create mask descriptor pool for window frame " + std::to_string(i)); + } + mask_descriptor_pools_[i] = pool; + } + } + + // ========================================================================= + // 后处理描述符池配置 + // 参考 post_effect_applicator.cpp 中的配置: + // - 32 个 CombinedImageSampler + // - 32 个 UniformBuffer + // - maxSets = 64 + // ========================================================================= + { + std::array pool_sizes = { + { + {vk::DescriptorType::eCombinedImageSampler, 32}, + {vk::DescriptorType::eUniformBuffer, 32} + } + }; + + vk::DescriptorPoolCreateInfo pool_info{ + vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + 64, + static_cast(pool_sizes.size()), + pool_sizes.data() + }; + + for (uint32_t i = 0; i < frames_in_flight_; ++i) { + auto [pool_result, pool] = device_handle.createDescriptorPool(pool_info); + if (pool_result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create post effect descriptor pool for window frame " + std::to_string(i)); + } + post_effect_descriptor_pools_[i] = pool; + } + } + } + + auto window_render_context::destroy_renderer_descriptor_pools() -> void { + if (!device_) { + return; + } + + auto device_handle = device_->get_handle(); + + for (auto& pool : text_descriptor_pools_) { + if (pool) { + device_handle.destroyDescriptorPool(pool); + } + } + text_descriptor_pools_.clear(); + + for (auto& pool : image_descriptor_pools_) { + if (pool) { + device_handle.destroyDescriptorPool(pool); + } + } + image_descriptor_pools_.clear(); + + for (auto& pool : mask_descriptor_pools_) { + if (pool) { + device_handle.destroyDescriptorPool(pool); + } + } + mask_descriptor_pools_.clear(); + + for (auto& pool : post_effect_descriptor_pools_) { + if (pool) { + device_handle.destroyDescriptorPool(pool); + } + } + post_effect_descriptor_pools_.clear(); + } + + auto window_render_context::get_text_descriptor_pool(uint32_t frame_index) const -> vk::DescriptorPool { + if (frame_index >= text_descriptor_pools_.size()) { + return nullptr; + } + return text_descriptor_pools_[frame_index]; + } + + auto window_render_context::get_image_descriptor_pool(uint32_t frame_index) const -> vk::DescriptorPool { + if (frame_index >= image_descriptor_pools_.size()) { + return nullptr; + } + return image_descriptor_pools_[frame_index]; + } + + auto window_render_context::get_mask_descriptor_pool(uint32_t frame_index) const -> vk::DescriptorPool { + if (frame_index >= mask_descriptor_pools_.size()) { + return nullptr; + } + return mask_descriptor_pools_[frame_index]; + } + + auto window_render_context::get_post_effect_descriptor_pool(uint32_t frame_index) const -> vk::DescriptorPool { + if (frame_index >= post_effect_descriptor_pools_.size()) { + return nullptr; + } + return post_effect_descriptor_pools_[frame_index]; + } + + auto window_render_context::reset_descriptor_pools(uint32_t frame_index) -> void { + if (!device_) { + return; + } + + auto device_handle = device_->get_handle(); + + // 重置文本渲染器描述符池 + if (frame_index < text_descriptor_pools_.size() && text_descriptor_pools_[frame_index]) { + (void)device_handle.resetDescriptorPool(text_descriptor_pools_[frame_index]); + } + + // 重置图像渲染器描述符池 + if (frame_index < image_descriptor_pools_.size() && image_descriptor_pools_[frame_index]) { + (void)device_handle.resetDescriptorPool(image_descriptor_pools_[frame_index]); + } + + // 重置遮罩渲染器描述符池 + if (frame_index < mask_descriptor_pools_.size() && mask_descriptor_pools_[frame_index]) { + (void)device_handle.resetDescriptorPool(mask_descriptor_pools_[frame_index]); + } + + // 重置后处理描述符池 + if (frame_index < post_effect_descriptor_pools_.size() && post_effect_descriptor_pools_[frame_index]) { + (void)device_handle.resetDescriptorPool(post_effect_descriptor_pools_[frame_index]); + } + } + // ============================================================================ // 内部方法 - 资源销毁 // ============================================================================ @@ -545,8 +950,26 @@ namespace mirage { } blit_descriptor_sets_.clear(); + // 6. 销毁帧栅栏 + for (auto& fence : frame_fences_) { + if (fence) { + device_->get_handle().destroyFence(fence); + } + } + frame_fences_.clear(); + + // 7. 销毁命令池(会自动释放命令缓冲) + if (command_pool_) { + device_->get_handle().destroyCommandPool(command_pool_); + command_pool_ = nullptr; + } + command_buffers_.clear(); + + // 8. 销毁渲染器描述符池 + destroy_renderer_descriptor_pools(); + // 注意:Surface 的销毁需要 VkInstance,由外部管理 // 此处不销毁 surface_,只是将其置空 surface_ = nullptr; } -} // namespace mirage + } // namespace mirage diff --git a/src/render/pipeline/window_render_context.h b/src/render/pipeline/window_render_context.h index 134473a..7b30d7e 100644 --- a/src/render/pipeline/window_render_context.h +++ b/src/render/pipeline/window_render_context.h @@ -16,7 +16,7 @@ namespace mirage { // window_id 类型已在 common/window_id.h 中定义于 mirage 命名空间 /// 窗口渲染上下文 - 封装每窗口独立的 Vulkan 资源 - /// + /// /// 职责: /// 1. 管理窗口的 VkSurfaceKHR 和 swapchain /// 2. 创建和管理 swapchain 关联的 RenderPass(clear/load 模式) @@ -25,6 +25,7 @@ namespace mirage { /// 5. 管理离屏渲染目标(offscreen_target) /// 6. 管理 Blit 描述符池和描述符集 /// 7. 追踪窗口状态(image_initialized、image_consistent) + /// 8. 管理每窗口独立的渲染器描述符池(解决多窗口同步问题) class window_render_context { public: /// 构造函数 @@ -136,6 +137,39 @@ namespace mirage { /// 推进帧索引 auto advance_frame() -> void; + // ===================================================================== + // 命令缓冲和帧同步(每窗口独立) + // ===================================================================== + + /// 获取指定帧的命令缓冲 + /// @param frame_index 帧索引 + [[nodiscard]] auto get_command_buffer(uint32_t frame_index) const -> vk::CommandBuffer; + + /// 获取命令池 + [[nodiscard]] auto get_command_pool() const noexcept -> vk::CommandPool { return command_pool_; } + + /// 开始命令缓冲录制 + /// @param frame_index 帧索引 + /// @return 开始录制的 CommandBuffer + [[nodiscard]] auto begin_command_buffer(uint32_t frame_index) -> vk::CommandBuffer; + + /// 结束命令缓冲录制 + /// @param frame_index 帧索引 + auto end_command_buffer(uint32_t frame_index) -> void; + + /// 等待指定帧的 Fence + /// @param frame_index 帧索引 + /// @return 是否成功(超时返回 false) + [[nodiscard]] auto wait_for_frame(uint32_t frame_index) -> bool; + + /// 重置指定帧的 Fence + /// @param frame_index 帧索引 + auto reset_frame_fence(uint32_t frame_index) -> void; + + /// 获取指定帧的 Fence + /// @param frame_index 帧索引 + [[nodiscard]] auto get_frame_fence(uint32_t frame_index) const -> vk::Fence; + // ===================================================================== // 状态追踪 // ===================================================================== @@ -168,6 +202,30 @@ namespace mirage { /// @param blit_sampler blit 采样器 auto update_blit_descriptors(vk::Sampler blit_sampler) -> void; + // ===================================================================== + // 每窗口独立的渲染器描述符池 + // ===================================================================== + + /// 获取当前帧的文本渲染器描述符池 + /// @param frame_index 帧索引 + [[nodiscard]] auto get_text_descriptor_pool(uint32_t frame_index) const -> vk::DescriptorPool; + + /// 获取当前帧的图像渲染器描述符池 + /// @param frame_index 帧索引 + [[nodiscard]] auto get_image_descriptor_pool(uint32_t frame_index) const -> vk::DescriptorPool; + + /// 获取当前帧的遮罩渲染器描述符池 + /// @param frame_index 帧索引 + [[nodiscard]] auto get_mask_descriptor_pool(uint32_t frame_index) const -> vk::DescriptorPool; + + /// 获取当前帧的后处理描述符池 + /// @param frame_index 帧索引 + [[nodiscard]] auto get_post_effect_descriptor_pool(uint32_t frame_index) const -> vk::DescriptorPool; + + /// 重置当前帧的所有渲染器描述符池 + /// @param frame_index 帧索引 + auto reset_descriptor_pools(uint32_t frame_index) -> void; + private: // ===================================================================== // 内部方法 @@ -186,6 +244,18 @@ namespace mirage { /// @param layout 描述符集布局 auto create_blit_descriptors(vk::DescriptorSetLayout layout) -> void; + /// 创建命令资源(Pool 和 Buffer) + auto create_command_resources() -> void; + + /// 创建帧同步对象(Fence) + auto create_frame_fences() -> void; + + /// 创建渲染器描述符池资源 + auto create_renderer_descriptor_pools() -> void; + + /// 销毁渲染器描述符池资源 + auto destroy_renderer_descriptor_pools() -> void; + /// 销毁 swapchain 相关资源(Framebuffer、RenderPass) auto destroy_swapchain_resources() -> void; @@ -226,6 +296,21 @@ namespace mirage { /// Blit 描述符集(每帧一个) std::vector blit_descriptor_sets_; + /// 命令池(每窗口独立) + vk::CommandPool command_pool_{nullptr}; + + /// 命令缓冲(每帧一个) + std::vector command_buffers_; + + /// 帧栅栏(每帧一个) + std::vector frame_fences_; + + /// Fence 等待超时(纳秒) + static constexpr uint64_t fence_timeout_ns_ = 100'000'000; + + /// Fence 超时最大重试次数 + static constexpr uint32_t max_fence_retries_ = 3; + /// 图像初始化状态追踪 std::vector image_initialized_; @@ -240,5 +325,21 @@ namespace mirage { /// 保存的 blit 描述符布局(用于重建) vk::DescriptorSetLayout blit_desc_layout_{nullptr}; + + // ===================================================================== + // 每窗口独立的渲染器描述符池 + // ===================================================================== + + /// 文本渲染器描述符池(每帧一个) + std::vector text_descriptor_pools_; + + /// 图像渲染器描述符池(每帧一个) + std::vector image_descriptor_pools_; + + /// 遮罩渲染器描述符池(每帧一个) + std::vector mask_descriptor_pools_; + + /// 后处理描述符池(每帧一个) + std::vector post_effect_descriptor_pools_; }; } // namespace mirage diff --git a/src/render/renderers/geometry_renderer.h b/src/render/renderers/geometry_renderer.h index e279404..5df612a 100644 --- a/src/render/renderers/geometry_renderer.h +++ b/src/render/renderers/geometry_renderer.h @@ -13,7 +13,6 @@ #include #include #include -#include namespace mirage { /// 几何渲染器 - 渲染基础几何图形到离屏目标 @@ -125,15 +124,6 @@ namespace mirage { vk::Pipeline pipeline_; ///< 矩形渲染管线 vk::PipelineLayout layout_; ///< 矩形渲染管线布局 - // 图片渲染管线资源 - vk::Pipeline image_pipeline_; - vk::PipelineLayout image_layout_; - vk::DescriptorSetLayout image_desc_layout_; - vk::DescriptorPool desc_pool_; - - // 纹理 Descriptor Set 缓存 - std::unordered_map texture_sets_; - // 每帧资源 struct frame_data { typed_buffer_owned vertices; @@ -155,10 +145,6 @@ namespace mirage { // 初始化辅助方法 void create_pipeline(vk::RenderPass render_pass); - void create_image_pipeline(vk::RenderPass render_pass); void create_frame_resources(); - - /// @brief 获取或创建纹理的 Descriptor Set - auto get_texture_descriptor_set(uint32_t texture_id) -> vk::DescriptorSet; }; } // namespace mirage diff --git a/src/render/renderers/image_renderer.cpp b/src/render/renderers/image_renderer.cpp index 849363e..6e56309 100644 --- a/src/render/renderers/image_renderer.cpp +++ b/src/render/renderers/image_renderer.cpp @@ -52,44 +52,13 @@ namespace mirage { } desc_layout_ = layout; - // 2. 创建描述符池(每帧独立的池) - create_descriptor_pools(); + // 描述符池由 window_render_context 管理,不再在这里创建 - // 3. 从 sampler_cache 获取 Sampler(线性过滤,边缘钳制) + // 从 sampler_cache 获取 Sampler(线性过滤,边缘钳制) // Sampler 由缓存管理,不需要手动销毁 sampler_ = res_mgr_.get_sampler_cache().linear_clamp(); } - void image_renderer::create_descriptor_pools() { - auto device_handle = device_.get_handle(); - - // 为每帧创建独立的描述符池 - descriptor_pools_.resize(config_.frames_in_flight); - - vk::DescriptorPoolSize pool_size{}; - pool_size.type = vk::DescriptorType::eCombinedImageSampler; - pool_size.descriptorCount = config_.max_textures_per_frame; - - vk::DescriptorPoolCreateInfo pool_info{}; - pool_info.poolSizeCount = 1; - pool_info.pPoolSizes = &pool_size; - pool_info.maxSets = config_.max_textures_per_frame; - // 不需要 eFreeDescriptorSet 标志,因为我们使用 resetDescriptorPool - - for (uint32_t i = 0; i < config_.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 image_renderer::create_pipeline(vk::RenderPass render_pass) { auto device_handle = device_.get_handle(); @@ -314,28 +283,20 @@ namespace mirage { } } - void image_renderer::begin_frame(uint32_t frame_index, const vec2f_t& viewport_size) { + void image_renderer::begin_frame(uint32_t frame_index, const vec2f_t& viewport_size, vk::DescriptorPool external_pool) { current_frame_ = frame_index % config_.frames_in_flight; current_frame_index_ = current_frame_; viewport_size_ = viewport_size; - // 重置当前帧的描述符池 - // - // 同步保证: - // 此函数在 render_pipeline::render_frame() 中被调用,调用时机在 - // frame_scheduler::begin_frame() 之后(该函数已等待此帧的fence)。 - // 这确保了上一轮该帧索引的GPU工作已完成,因此重置描述符池是安全的。 - auto device_handle = device_.get_handle(); - if (current_frame_index_ < descriptor_pools_.size()) { - (void)device_handle.resetDescriptorPool(descriptor_pools_[current_frame_index_]); - } + // 保存外部池引用(由 window_render_context 管理和重置) + current_external_pool_ = external_pool; // 重置当前帧的资源使用计数 frame_data_[current_frame_].used_vertex_count = 0; frame_data_[current_frame_].used_index_count = 0; } - auto image_renderer::allocate_texture_descriptor(uint32_t texture_id) -> vk::DescriptorSet { + auto image_renderer::allocate_texture_descriptor_from_pool(uint32_t texture_id, vk::DescriptorPool pool) -> vk::DescriptorSet { // 获取纹理视图 const auto view_opt = tex_mgr_.get_texture(texture_id); if (!view_opt) { @@ -343,25 +304,25 @@ namespace mirage { throw std::runtime_error("Texture not found: " + std::to_string(texture_id)); } const auto& texture_view = view_opt->image_view(); // 使用方法调用 - - // 从当前帧的描述符池分配 Descriptor Set + + // 从指定的描述符池分配 Descriptor Set vk::DescriptorSetAllocateInfo alloc_info{}; - alloc_info.descriptorPool = descriptor_pools_[current_frame_index_]; + alloc_info.descriptorPool = pool; alloc_info.descriptorSetCount = 1; alloc_info.pSetLayouts = &desc_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 texture (pool may be exhausted)"); } vk::DescriptorSet desc_set = sets[0]; - + // 更新 Descriptor Set vk::DescriptorImageInfo image_info{}; image_info.sampler = sampler_; image_info.imageView = texture_view; image_info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - + vk::WriteDescriptorSet write{}; write.dstSet = desc_set; write.dstBinding = 0; @@ -369,9 +330,9 @@ namespace mirage { write.descriptorType = vk::DescriptorType::eCombinedImageSampler; write.descriptorCount = 1; write.pImageInfo = &image_info; - + device_.get_handle().updateDescriptorSets(1, &write, 0, nullptr); - + return desc_set; } @@ -457,7 +418,7 @@ namespace mirage { // 绑定 Descriptor Set (纹理) try { - vk::DescriptorSet desc_set = allocate_texture_descriptor(batch.texture_id.value()); + vk::DescriptorSet desc_set = allocate_texture_descriptor_from_pool(batch.texture_id.value(), current_external_pool_); cmd.bindDescriptorSets( vk::PipelineBindPoint::eGraphics, layout_, @@ -543,13 +504,7 @@ namespace mirage { layout_ = nullptr; } - // 清理所有描述符池 - for (auto& pool : descriptor_pools_) { - if (pool) { - device_handle.destroyDescriptorPool(pool); - } - } - descriptor_pools_.clear(); + // 描述符池由 window_render_context 管理,不需要在这里清理 if (desc_layout_) { device_handle.destroyDescriptorSetLayout(desc_layout_); diff --git a/src/render/renderers/image_renderer.h b/src/render/renderers/image_renderer.h index 47c124a..94fa5f1 100644 --- a/src/render/renderers/image_renderer.h +++ b/src/render/renderers/image_renderer.h @@ -48,11 +48,12 @@ namespace mirage { /// 初始化 Pipeline /// @param render_pass 渲染通道(来自 offscreen_target) void initialize(vk::RenderPass render_pass); - + /// 开始新帧 /// @param frame_index 帧索引 /// @param viewport_size 视口大小 - void begin_frame(uint32_t frame_index, const vec2f_t& viewport_size); + /// @param external_pool 外部描述符池(必须提供,由 window_render_context 管理) + void begin_frame(uint32_t frame_index, const vec2f_t& viewport_size, vk::DescriptorPool external_pool); // ======================================================================== // 类型安全接口 - 使用 frame_context @@ -120,11 +121,10 @@ namespace mirage { config config_; // Pipeline 资源 - vk::Pipeline pipeline_; ///< 图片渲染管线 - vk::PipelineLayout layout_; ///< 管线布局 - vk::DescriptorSetLayout desc_layout_; ///< 描述符布局 - std::vector descriptor_pools_; ///< 每帧独立的描述符池 - vk::Sampler sampler_; ///< 纹理采样器 + vk::Pipeline pipeline_; ///< 图片渲染管线 + vk::PipelineLayout layout_; ///< 管线布局 + vk::DescriptorSetLayout desc_layout_; ///< 描述符布局 + vk::Sampler sampler_; ///< 纹理采样器 // 每帧资源 struct frame_data { @@ -145,13 +145,16 @@ namespace mirage { // 初始化辅助方法 void create_pipeline(vk::RenderPass render_pass); - void create_descriptor_pools(); void create_descriptors(); void create_frame_resources(); - /// @brief 为纹理分配描述符集 + /// @brief 从指定池分配纹理描述符集 /// @param texture_id 纹理 ID + /// @param pool 描述符池 /// @return 对应的 Descriptor Set - auto allocate_texture_descriptor(uint32_t texture_id) -> vk::DescriptorSet; + auto allocate_texture_descriptor_from_pool(uint32_t texture_id, vk::DescriptorPool pool) -> vk::DescriptorSet; + + /// 当前使用的外部描述符池 + vk::DescriptorPool current_external_pool_{nullptr}; }; } // namespace mirage diff --git a/src/render/renderers/mask_renderer.cpp b/src/render/renderers/mask_renderer.cpp index b44c816..bd7a555 100644 --- a/src/render/renderers/mask_renderer.cpp +++ b/src/render/renderers/mask_renderer.cpp @@ -210,8 +210,6 @@ namespace mirage { // ============================================================================ void mask_renderer::create_frame_resources() { - auto device_handle = device_.get_handle(); - constexpr uint32_t vertices_per_mask = 6; constexpr uint32_t indices_per_mask = 6; constexpr uint32_t max_masks = 16; @@ -239,26 +237,11 @@ namespace mirage { throw std::runtime_error("Failed to create index buffer for mask renderer"); } - // 为每帧创建独立的描述符池 - vk::DescriptorPoolSize pool_size{}; - pool_size.type = vk::DescriptorType::eCombinedImageSampler; - pool_size.descriptorCount = max_masks; - - vk::DescriptorPoolCreateInfo pool_info{}; - pool_info.poolSizeCount = 1; - pool_info.pPoolSizes = &pool_size; - pool_info.maxSets = max_masks; - // 不使用 eFreeDescriptorSet,因为我们使用 resetDescriptorPool 整体重置 - - auto [pool_result, pool] = device_handle.createDescriptorPool(pool_info); - if (pool_result != vk::Result::eSuccess) { - throw std::runtime_error("Failed to create descriptor pool for mask renderer frame resources"); - } + // 描述符池由 window_render_context 管理,不再在这里创建 frame_data_.emplace_back(frame_data{ typed_buffer_owned(std::move(vb_result.value())), - typed_buffer_owned(std::move(ib_result.value())), - pool + typed_buffer_owned(std::move(ib_result.value())) }); } } @@ -267,16 +250,16 @@ namespace mirage { // 帧管理 // ============================================================================ - void mask_renderer::begin_frame(uint32_t frame_index, const vec2f_t& viewport_size) { + void mask_renderer::begin_frame(uint32_t frame_index, const vec2f_t& viewport_size, vk::DescriptorPool external_pool) { current_frame_ = frame_index % config_.frames_in_flight; viewport_size_ = viewport_size; + // 保存外部池引用(由 window_render_context 管理和重置) + current_external_pool_ = external_pool; + auto& frame = frame_data_[current_frame_]; frame.used_vertex_count = 0; frame.used_index_count = 0; - - // 重置当前帧的描述符池,释放所有之前分配的描述符集 - (void)device_.get_handle().resetDescriptorPool(frame.desc_pool); } void mask_renderer::end_frame() { @@ -519,23 +502,23 @@ namespace mirage { // 描述符分配 // ============================================================================ - auto mask_renderer::allocate_descriptor_set(vk::ImageView image_view) -> vk::DescriptorSet { + auto mask_renderer::allocate_descriptor_set_from_pool(vk::ImageView image_view, vk::DescriptorPool pool) -> vk::DescriptorSet { vk::DescriptorSetAllocateInfo alloc_info{}; - alloc_info.descriptorPool = frame_data_[current_frame_].desc_pool; + alloc_info.descriptorPool = pool; alloc_info.descriptorSetCount = 1; alloc_info.pSetLayouts = &desc_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 mask renderer"); } vk::DescriptorSet desc_set = sets[0]; - + vk::DescriptorImageInfo image_info{}; image_info.sampler = sampler_; image_info.imageView = image_view; image_info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - + vk::WriteDescriptorSet write{}; write.dstSet = desc_set; write.dstBinding = 0; @@ -543,9 +526,9 @@ namespace mirage { write.descriptorType = vk::DescriptorType::eCombinedImageSampler; write.descriptorCount = 1; write.pImageInfo = &image_info; - + device_.get_handle().updateDescriptorSets(1, &write, 0, nullptr); - + return desc_set; } @@ -581,7 +564,7 @@ namespace mirage { }; cmd.setScissor(0, 1, &scissor); - auto desc_set = allocate_descriptor_set(mask_texture_view); + auto desc_set = allocate_descriptor_set_from_pool(mask_texture_view, current_external_pool_); cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout_, 0, 1, &desc_set, 0, nullptr); @@ -763,12 +746,8 @@ namespace mirage { // sampler_ 由 sampler_cache 管理,不需要手动销毁 sampler_ = nullptr; - for (auto& frame : frame_data_) { - if (frame.desc_pool) { - device_handle.destroyDescriptorPool(frame.desc_pool); - } - // typed_buffer_owned 会自动释放底层资源 - } + // 描述符池由 window_render_context 管理,不需要在这里清理 + // typed_buffer_owned 会自动释放底层资源 frame_data_.clear(); // 清理活跃句柄栈(RAII 自动释放) diff --git a/src/render/renderers/mask_renderer.h b/src/render/renderers/mask_renderer.h index c028eef..0a80ac3 100644 --- a/src/render/renderers/mask_renderer.h +++ b/src/render/renderers/mask_renderer.h @@ -90,7 +90,8 @@ namespace mirage { /// @brief 开始新帧 /// @param frame_index 帧索引 /// @param viewport_size 视口大小 - void begin_frame(uint32_t frame_index, const vec2f_t& viewport_size); + /// @param external_pool 外部描述符池(必须提供,由 window_render_context 管理) + void begin_frame(uint32_t frame_index, const vec2f_t& viewport_size, vk::DescriptorPool external_pool); /// @brief 结束当前帧 void end_frame(); @@ -251,10 +252,14 @@ namespace mirage { auto generate_mask_vertices(const mask_params& params, const aabb2d_t& bounds) -> std::array; - /// @brief 为纹理分配描述符集 + /// @brief 从指定池分配描述符集 /// @param image_view 要绑定的图像视图 + /// @param pool 描述符池 /// @return 分配的描述符集 - auto allocate_descriptor_set(vk::ImageView image_view) -> vk::DescriptorSet; + auto allocate_descriptor_set_from_pool(vk::ImageView image_view, vk::DescriptorPool pool) -> vk::DescriptorSet; + + /// 当前使用的外部描述符池 + vk::DescriptorPool current_external_pool_{nullptr}; private: // ======================================================================== @@ -301,7 +306,6 @@ namespace mirage { struct frame_data { typed_buffer_owned vertices; ///< 顶点缓冲 typed_buffer_owned indices; ///< 索引缓冲 - vk::DescriptorPool desc_pool; ///< 每帧描述符池 uint32_t used_vertex_count = 0; ///< 已使用的顶点数 uint32_t used_index_count = 0; ///< 已使用的索引数 }; diff --git a/src/render/renderers/text_renderer.cpp b/src/render/renderers/text_renderer.cpp index b3b7a62..1630db1 100644 --- a/src/render/renderers/text_renderer.cpp +++ b/src/render/renderers/text_renderer.cpp @@ -31,7 +31,6 @@ namespace mirage { void text_renderer::initialize(vk::RenderPass render_pass) { create_descriptor_set_layouts(); - create_descriptor_pool(); create_frame_resources(); // 加载标准MTSDF着色器并创建管线 @@ -39,14 +38,9 @@ namespace mirage { standard_pipeline_ = create_pipeline(render_pass, frag_spirv); // 初始化描述符集数组和有效性标记 + // 描述符集将在 begin_frame 中从外部池分配 atlas_descriptor_sets_.resize(frames_in_flight_); descriptor_set_valid_.resize(frames_in_flight_, false); - - // 为所有帧预分配描述符集 - // 注意:此时图集可能还没有页面,但我们需要预分配描述符集以避免首帧渲染时的空指针 - for (uint32_t i = 0; i < frames_in_flight_; ++i) { - atlas_descriptor_sets_[i] = allocate_descriptor_set(i); - } } void text_renderer::create_descriptor_set_layouts() { @@ -91,38 +85,6 @@ namespace mirage { params_set_layout_ = params_layout; } - void text_renderer::create_descriptor_pool() { - auto device_handle = device_.get_handle(); - - // 使用实际的 frames_in_flight_ 而不是硬编码的 MAX_FRAMES_IN_FLIGHT - descriptor_pools_.resize(frames_in_flight_); - - constexpr uint32_t MAX_ATLAS_PAGES = 16; - - std::array pool_sizes{}; - pool_sizes[0].type = vk::DescriptorType::eCombinedImageSampler; - pool_sizes[0].descriptorCount = MAX_ATLAS_PAGES * 2; // 支持多页面图集 - 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 < 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() { // 使用实际的 frames_in_flight_ 而不是硬编码的 MAX_FRAMES_IN_FLIGHT frame_data_.reserve(frames_in_flight_); @@ -356,19 +318,15 @@ namespace mirage { return pipeline; } - void text_renderer::begin_frame(uint32_t frame_index, const vec2f_t& viewport_size) { + void text_renderer::begin_frame(uint32_t frame_index, const vec2f_t& viewport_size, vk::DescriptorPool external_pool) { // 使用实际的 frames_in_flight_ 计算帧索引 current_frame_ = frame_index % frames_in_flight_; viewport_size_ = viewport_size; - // 重置描述符池 - auto device_handle = device_.get_handle(); - if (current_frame_ < descriptor_pools_.size()) { - (void)device_handle.resetDescriptorPool(descriptor_pools_[current_frame_]); - } - - // 重新分配当前帧的描述符集(重置后需要重新分配) - atlas_descriptor_sets_[current_frame_] = allocate_descriptor_set(current_frame_); + // 使用外部池(由 window_render_context 管理和重置) + // 不需要在这里重置,调用者已经负责重置 + // 重新分配当前帧的描述符集 + atlas_descriptor_sets_[current_frame_] = allocate_descriptor_set_from_pool(external_pool); // 重置资源使用计数 frame_data_[current_frame_].used_vertex_count = 0; @@ -582,17 +540,17 @@ namespace mirage { frame_res.used_index_count += static_cast(batch.indices.size()); } - auto text_renderer::allocate_descriptor_set(uint32_t frame_index) -> vk::DescriptorSet { + auto text_renderer::allocate_descriptor_set_from_pool(vk::DescriptorPool pool) -> vk::DescriptorSet { vk::DescriptorSetAllocateInfo alloc_info{}; - alloc_info.descriptorPool = descriptor_pools_[frame_index]; + alloc_info.descriptorPool = pool; 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]; } @@ -651,14 +609,7 @@ namespace mirage { pipeline_layout_ = nullptr; } - // 清理描述符 - for (auto& pool : descriptor_pools_) { - if (pool) { - device_handle.destroyDescriptorPool(pool); - } - } - descriptor_pools_.clear(); - + // 清理描述符布局(描述符池由 window_render_context 管理) if (atlas_set_layout_) { device_handle.destroyDescriptorSetLayout(atlas_set_layout_); atlas_set_layout_ = nullptr; diff --git a/src/render/renderers/text_renderer.h b/src/render/renderers/text_renderer.h index 2c6aad6..c2627b2 100644 --- a/src/render/renderers/text_renderer.h +++ b/src/render/renderers/text_renderer.h @@ -72,9 +72,12 @@ namespace mirage { // 初始化渲染器(创建管线等) void initialize(vk::RenderPass render_pass); - - // 帧开始时同步各组件状态 - void begin_frame(uint32_t frame_index, const vec2f_t& viewport_size); + + /// 帧开始时同步各组件状态 + /// @param frame_index 帧索引 + /// @param viewport_size 视口大小 + /// @param external_pool 外部描述符池(必须提供,由 window_render_context 管理) + void begin_frame(uint32_t frame_index, const vec2f_t& viewport_size, vk::DescriptorPool external_pool); // 上传纹理(在render pass之前调用) void upload_textures(vk::CommandBuffer cmd); @@ -129,14 +132,11 @@ namespace mirage { // 创建描述符集布局 void create_descriptor_set_layouts(); - // 创建描述符池 - void create_descriptor_pool(); - // 创建帧资源 void create_frame_resources(); - // 分配描述符集 - auto allocate_descriptor_set(uint32_t frame_index) -> vk::DescriptorSet; + // 从指定池分配描述符集 + auto allocate_descriptor_set_from_pool(vk::DescriptorPool pool) -> vk::DescriptorSet; // 更新描述符集(支持多页面图集) void update_descriptors(); @@ -161,9 +161,8 @@ namespace mirage { std::unordered_map custom_pipelines_; // 描述符相关 - vk::DescriptorSetLayout atlas_set_layout_; // set 0: 图集纹理(数组绑定) - vk::DescriptorSetLayout params_set_layout_; // set 1: 自定义参数 - std::vector descriptor_pools_; + vk::DescriptorSetLayout atlas_set_layout_; // set 0: 图集纹理(数组绑定) + vk::DescriptorSetLayout params_set_layout_; // set 1: 自定义参数 // 当前描述符集(每帧更新) std::vector atlas_descriptor_sets_; diff --git a/src/render/text/glyph_cache.cpp b/src/render/text/glyph_cache.cpp index 281760f..823d1c0 100644 --- a/src/render/text/glyph_cache.cpp +++ b/src/render/text/glyph_cache.cpp @@ -277,6 +277,7 @@ namespace mirage { } bool has_new_glyphs = false; + uint32_t new_glyph_count = 0; for (auto& result : results) { // 查找对应的用户键(不删除映射,稍后删除) @@ -317,6 +318,7 @@ namespace mirage { if (entry_id_opt) { has_new_glyphs = true; + ++new_glyph_count; // 【关键修复】只有在成功分配后才删除任务映射 // 这确保在整个过程完成之前,get_or_request_glyph 能看到任务仍在进行中 @@ -345,6 +347,11 @@ namespace mirage { } } + // 如果有新字形生成完成,递增版本号 + if (has_new_glyphs) { + cache_version_.fetch_add(new_glyph_count, std::memory_order_release); + } + return has_new_glyphs; } diff --git a/src/render/text/glyph_cache.h b/src/render/text/glyph_cache.h index 9b0f6f1..b3e30fb 100644 --- a/src/render/text/glyph_cache.h +++ b/src/render/text/glyph_cache.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -215,6 +216,27 @@ namespace mirage { return glyph_config::calculate_pixel_range(glyph_size_); } + /** + * @brief 检查是否有待处理的字形生成任务 + * @return 如果有待处理任务返回true + */ + [[nodiscard]] auto has_pending_tasks() const -> bool { + std::shared_lock lock(task_mutex_); + return !task_to_key_.empty(); + } + + /** + * @brief 获取字形缓存版本号 + * + * 每当有新字形生成完成时,版本号会递增。 + * 窗口可以通过比较版本号来判断是否需要重绘。 + * + * @return 当前版本号 + */ + [[nodiscard]] auto get_version() const -> uint64_t { + return cache_version_.load(std::memory_order_acquire); + } + private: /** * @brief 淘汰回调处理 @@ -294,5 +316,8 @@ namespace mirage { // 线程安全保护 mutable std::shared_mutex metrics_mutex_; ///< 保护metrics_cache_ mutable std::shared_mutex task_mutex_; ///< 保护task_to_key_和key_to_task_ + + // 缓存版本号,每当有新字形生成完成时递增 + std::atomic cache_version_{0}; }; } // namespace mirage diff --git a/src/render/text/text_shaper.cpp b/src/render/text/text_shaper.cpp index 7d73d12..6a39e88 100644 --- a/src/render/text/text_shaper.cpp +++ b/src/render/text/text_shaper.cpp @@ -94,8 +94,15 @@ namespace mirage { // 使用异步API获取字形(添加font_size参数) auto lookup_result = cache_.get_or_request_glyph(font_id, codepoint, font_size); - // 只处理已就绪的字形,pending状态的字形将在下一帧可用 + // 处理未就绪的字形:仍然推进光标位置,但不生成顶点 if (lookup_result.state != glyph_state::ready || !lookup_result.glyph) [[unlikely]] { + // 字形未就绪,使用估算的 advance 值推进光标位置 + // 使用字体的平均字符宽度(约为 line_height 的 0.5 倍)作为估算值 + // 这是一个合理的近似值,适用于大多数字体 + // 注意:不能调用 font_manager::load_glyph,因为它不是线程安全的 + float estimated_advance = font_metrics.line_height * 0.5f; + cursor_x += estimated_advance; + ++glyph_counter; continue; } @@ -225,22 +232,23 @@ namespace mirage { // 使用 ascender - descender 作为高度,确保测量与渲染一致 const auto font_metrics = font_mgr_.get_font_metrics(font_id, static_cast(font_size)); - // 使用 ranges 和算法优化测量过程 - auto glyphs_view = text - | std::views::transform([this, font_id, font_size](char32_t cp) { - auto result = cache_.get_or_request_glyph(font_id, cp, font_size); - return (result.state == glyph_state::ready && result.glyph) ? &result.glyph->metrics : nullptr; - }) - | std::views::filter([](const auto* ptr) { return ptr != nullptr; }); + // 估算的 advance 值(用于未就绪的字形) + const float estimated_advance = font_metrics.line_height * 0.5f; - // 使用 fold 表达式计算总宽度 - const float total_width = std::ranges::fold_left( - glyphs_view | std::views::transform([scale](const auto* metrics_ptr) { - return metrics_ptr->advance * scale; - }), - 0.0f, - std::plus<>{} - ); + // 计算总宽度,包括未就绪的字形 + float total_width = 0.0f; + for (const auto codepoint : text) { + auto result = cache_.get_or_request_glyph(font_id, codepoint, font_size); + + if (result.state == glyph_state::ready && result.glyph) { + // 字形已就绪,使用缓存的 advance 值 + total_width += result.glyph->metrics.advance * scale; + } else { + // 字形未就绪,使用估算的 advance 值 + // 注意:不能调用 font_manager::load_glyph,因为它不是线程安全的 + total_width += estimated_advance; + } + } // 使用字体度量计算高度(与 shape_text 保持一致) // ascender 为正值,descender 为负值,所以 height = ascender - descender diff --git a/src/threading/render_thread.cpp b/src/threading/render_thread.cpp index e4e3a25..6406812 100644 --- a/src/threading/render_thread.cpp +++ b/src/threading/render_thread.cpp @@ -221,28 +221,25 @@ namespace mirage { auto now = std::chrono::steady_clock::now(); float time = std::chrono::duration(now - start_time_).count(); - // 获取所有已注册的窗口ID - auto window_ids = pipeline_.get_window_ids(); - - // 如果没有已注册的窗口,跳过渲染 - if (window_ids.empty()) { - return; + // 获取要渲染的窗口ID + window_id target_window = target_window_id_.load(std::memory_order_acquire); + + // 如果没有设置目标窗口,尝试使用主窗口 + if (target_window == INVALID_WINDOW_ID) { + auto primary_window = pipeline_.get_primary_window(); + if (!primary_window) { + return; + } + target_window = *primary_window; } - // 遍历所有窗口进行渲染 - // 使用 ranges 统计渲染失败的窗口数量 - auto render_results = window_ids - | std::views::transform([&](auto id) { - return pipeline_.render_window(id, state.tree, time, state.dirty_rect); - }); + // 渲染目标窗口 + bool success = pipeline_.render_window(target_window, state.tree, time, state.dirty_rect); - // 统计失败数量 - auto failed_count = std::ranges::count(render_results, false); - - // 如果有任何窗口渲染失败,记录丢帧 - if (failed_count > 0) { + // 如果渲染失败,记录丢帧 + if (!success) { std::lock_guard lock(stats_mutex_); - stats_.dropped_frames += static_cast(failed_count); + stats_.dropped_frames++; } } diff --git a/src/threading/render_thread.h b/src/threading/render_thread.h index 43e4710..81dbb52 100644 --- a/src/threading/render_thread.h +++ b/src/threading/render_thread.h @@ -16,6 +16,7 @@ #include "threading/sync_state.h" #include "threading/frame_sync.h" #include "core/types.h" +#include "ui/window/window_id.h" namespace mirage { class render_pipeline; @@ -159,6 +160,26 @@ namespace mirage { /// @param height 新的窗口高度 void notify_resize(uint32_t width, uint32_t height); + // ======================================================================== + // 目标窗口设置 + // ======================================================================== + + /// @brief 设置目标渲染窗口ID + /// + /// 设置此渲染线程应该渲染的窗口。如果不设置,将使用主窗口。 + /// + /// @param id 目标窗口ID + void set_target_window(window_id id) noexcept { + target_window_id_.store(id, std::memory_order_release); + } + + /// @brief 获取目标渲染窗口ID + /// + /// @return 目标窗口ID + [[nodiscard]] window_id get_target_window() const noexcept { + return target_window_id_.load(std::memory_order_acquire); + } + private: // ======================================================================== // 线程主函数 @@ -242,5 +263,8 @@ namespace mirage { /// @brief 累计帧时间(用于计算平均值) double total_frame_time_{0.0}; + + /// @brief 目标渲染窗口ID + std::atomic target_window_id_{INVALID_WINDOW_ID}; }; } // namespace mirage diff --git a/src/threading/thread_coordinator.cpp b/src/threading/thread_coordinator.cpp index 6b4ea46..7b6dcb8 100644 --- a/src/threading/thread_coordinator.cpp +++ b/src/threading/thread_coordinator.cpp @@ -52,10 +52,8 @@ namespace mirage { frames_completed_.store(0, std::memory_order_relaxed); current_frame_number_.store(0, std::memory_order_relaxed); - if (config_.enable_multithreading) { - // 多线程模式:创建同步原语和工作线程 - create_threads(config); - } + // 创建同步原语和工作线程 + create_threads(config); // 更新状态为运行中 state_.store(coordinator_state::running, std::memory_order_release); @@ -69,10 +67,8 @@ namespace mirage { return; } - if (config_.enable_multithreading) { - // 多线程模式:销毁工作线程 - destroy_threads(); - } + // 销毁工作线程 + destroy_threads(); // 更新状态为已停止 state_.store(coordinator_state::stopped, std::memory_order_release); @@ -109,14 +105,7 @@ namespace mirage { uint64_t frame_number = current_frame_number_.fetch_add(1, std::memory_order_relaxed); state_store_.begin_frame(frame_number); - if (!config_.enable_multithreading) { - // 单线程模式:直接处理 - process_frame_single_threaded(root, viewport_size); - state_store_.end_frame(); - return; - } - - // 多线程模式:创建轻量快照,布局计算将在布局线程执行 + // 检查布局线程是否可用 if (!layout_thread_ || !layout_thread_->is_running()) { state_store_.end_frame(); return; @@ -143,7 +132,7 @@ namespace mirage { } void thread_coordinator::process_completed_frames() { - if (!is_running() || !config_.enable_multithreading) { + if (!is_running()) { return; } @@ -175,14 +164,10 @@ namespace mirage { return; } - if (config_.enable_multithreading && render_thread_) { - // 多线程模式:通知渲染线程 + if (render_thread_) { + // 通知渲染线程重建 swapchain render_thread_->notify_resize(width, height); } - else { - // 单线程模式:直接调用渲染管线(使用兼容性接口处理主窗口) - pipeline_.perform_resize(width, height); - } } // ============================================================================ @@ -195,68 +180,25 @@ namespace mirage { stats.frames_submitted = frames_submitted_.load(std::memory_order_relaxed); stats.frames_completed = frames_completed_.load(std::memory_order_relaxed); - if (config_.enable_multithreading) { - // 从布局线程获取统计信息 - if (layout_thread_) { - auto layout_thread_stats = layout_thread_->get_stats(); - stats.layout.frames_processed = layout_thread_stats.frames_processed; - stats.layout.frames_dropped = layout_thread_stats.frames_dropped; - stats.layout.avg_frame_time_ms = layout_thread_stats.avg_frame_time_ms; - stats.layout.max_frame_time_ms = layout_thread_stats.max_frame_time_ms; - stats.layout.min_frame_time_ms = layout_thread_stats.min_frame_time_ms; - stats.frames_dropped = layout_thread_stats.frames_dropped; - } + // 从布局线程获取统计信息 + if (layout_thread_) { + auto layout_thread_stats = layout_thread_->get_stats(); + stats.layout.frames_processed = layout_thread_stats.frames_processed; + stats.layout.frames_dropped = layout_thread_stats.frames_dropped; + stats.layout.avg_frame_time_ms = layout_thread_stats.avg_frame_time_ms; + stats.layout.max_frame_time_ms = layout_thread_stats.max_frame_time_ms; + stats.layout.min_frame_time_ms = layout_thread_stats.min_frame_time_ms; + stats.frames_dropped = layout_thread_stats.frames_dropped; + } - // 从渲染线程获取统计信息 - if (render_thread_) { - stats.render = render_thread_->get_stats(); - } + // 从渲染线程获取统计信息 + if (render_thread_) { + stats.render = render_thread_->get_stats(); } return stats; } - // ============================================================================ - // 单线程模式 - // ============================================================================ - - void thread_coordinator::process_frame_single_threaded(widget_base* root, vec2f_t viewport_size) { - if (!root) { - return; - } - - // 优化:单线程模式下,帧号递增不需要同步 - uint64_t frame_number = current_frame_number_.fetch_add(1, std::memory_order_relaxed); - frames_submitted_.fetch_add(1, std::memory_order_relaxed); - - // 1. 测量阶段 - [[maybe_unused]] auto measure_result = root->measure(viewport_size); - - // 2. 布局阶段 - layout_state root_state; - root_state.set_root(transform2d_t::Identity(), viewport_size); - root->arrange(root_state); - - // 3. 收集渲染命令 - render_collector collector; - - // 设置视口缓存以支持视口剔除 - collector.set_viewport_cache(&context_.get_viewport_cache()); - - root->build_render_command(collector, 0); - - // 4. 渲染 - auto now = std::chrono::steady_clock::now(); - float time = std::chrono::duration(now - start_time_).count(); - [[maybe_unused]] auto render_result = pipeline_.render_frame(collector.get_render_commands(), time); - - // 5. 更新统计并发布帧完成事件 - frames_completed_.fetch_add(1, std::memory_order_relaxed); - - // 发布帧完成事件 - frame_complete_topic_.publish(frame_complete_event{frame_number}); - } - // ============================================================================ // 内部方法 // ============================================================================ @@ -283,7 +225,12 @@ namespace mirage { render_cfg.adaptive_sync = config.adaptive_sync; render_thread_->set_config(render_cfg); - // 5. 启动线程(先启动渲染线程,再启动布局线程) + // 5. 设置目标渲染窗口(如果已设置窗口ID) + if (window_id_ != INVALID_WINDOW_ID) { + render_thread_->set_target_window(window_id_); + } + + // 6. 启动线程(先启动渲染线程,再启动布局线程) render_thread_->start(); layout_thread_->start(); } diff --git a/src/threading/thread_coordinator.h b/src/threading/thread_coordinator.h index 2fafcb6..fd43cc9 100644 --- a/src/threading/thread_coordinator.h +++ b/src/threading/thread_coordinator.h @@ -18,6 +18,7 @@ #include "threading/pub_sub.h" #include "ui/state/widget_state_store.h" #include "ui/widgets/widget_context.h" +#include "ui/window/window_id.h" #include "render/pipeline/render_tree.h" #include "core/types.h" @@ -81,10 +82,9 @@ namespace mirage { /// /// 控制线程协调器的行为参数 struct coordinator_config { - int max_frames_in_flight = 3; ///< 最大飞行帧数 - bool enable_multithreading = true; ///< 是否启用多线程(可以禁用回退到单线程模式) - bool adaptive_sync = false; ///< 是否启用自适应同步 - render_config render_cfg; ///< 渲染配置 + int max_frames_in_flight = 3; ///< 最大飞行帧数 + bool adaptive_sync = false; ///< 是否启用自适应同步 + render_config render_cfg; ///< 渲染配置 }; // ============================================================================ @@ -140,7 +140,6 @@ namespace mirage { /// - 创建和管理 frame_sync、render_tree_buffer、layout_thread、render_thread /// - 处理帧提交:从控件树创建快照,提交给布局线程 /// - 处理帧完成:检查布局结果,触发回调 - /// - 支持单线程模式:当禁用多线程时,在主线程同步执行布局和渲染 /// - 处理窗口大小变化:通知渲染线程重建 swapchain /// /// @note 线程安全保证: @@ -215,8 +214,7 @@ namespace mirage { /// 1. 从控件树创建快照 /// 2. 提交给布局线程处理 /// - /// 在多线程模式下,此方法是非阻塞的。 - /// 在单线程模式下,此方法会同步执行布局和渲染。 + /// 此方法是非阻塞的,布局和渲染在工作线程中异步执行。 /// /// @param root 控件树的根节点 /// @param viewport_size 视口大小 @@ -230,8 +228,6 @@ namespace mirage { /// /// 主线程调用此方法检查是否有布局结果可用, /// 如果有,触发帧完成回调。 - /// - /// @note 只在多线程模式下有意义 void process_completed_frames(); // ======================================================================== @@ -294,17 +290,26 @@ namespace mirage { } // ======================================================================== - // 单线程模式 + // 窗口ID关联 // ======================================================================== - /// @brief 单线程模式下的帧处理 + /// @brief 设置关联的窗口ID /// - /// 当 enable_multithreading = false 时使用此方法。 - /// 在主线程同步执行布局和渲染。 + /// 使协调器知道自己管理哪个窗口,并更新渲染线程的目标窗口 /// - /// @param root 控件树的根节点 - /// @param viewport_size 视口大小 - void process_frame_single_threaded(widget_base* root, vec2f_t viewport_size); + /// @param id 窗口ID + void set_window_id(window_id id) noexcept { + window_id_ = id; + // 如果渲染线程已创建,同步更新其目标窗口 + if (render_thread_) { + render_thread_->set_target_window(id); + } + } + + /// @brief 获取关联的窗口ID + /// + /// @return 关联的窗口ID + [[nodiscard]] window_id get_window_id() const noexcept { return window_id_; } private: // ======================================================================== @@ -380,5 +385,8 @@ namespace mirage { /// @brief 协调器启动时间(用于计算真实时间) std::chrono::steady_clock::time_point start_time_; + + /// @brief 关联的窗口ID + window_id window_id_ = INVALID_WINDOW_ID; }; } // namespace mirage diff --git a/src/ui/tooltip/tooltip_manager.cpp b/src/ui/tooltip/tooltip_manager.cpp index 910db04..cdf6689 100644 --- a/src/ui/tooltip/tooltip_manager.cpp +++ b/src/ui/tooltip/tooltip_manager.cpp @@ -1,11 +1,17 @@ #include "tooltip_manager.h" -#include "tooltip_window_widget.h" +#include "ui/window/render_window.h" #include "ui/widgets/widget_base.h" #include "ui/widgets/single_child_widget_base.h" #include "ui/widgets/text_widget.h" #include "ui/widgets/fill_box.h" #include "core/types.h" +#include "core/vulkan_context_provider.h" +#include "core/common/str_utils.h" #include "ui/widgets/widget_context.h" +#include "ui/window/window_interface.h" +#include "render/text/glyph_cache.h" + +#include namespace mirage { namespace { @@ -133,7 +139,10 @@ namespace mirage { break; case state::showing: - // 无事可做,tooltip 正在显示 + // tooltip 正在显示,更新渲染窗口 + if (tooltip_render_window_ && tooltip_render_window_->is_visible()) { + tooltip_render_window_->tick(delta_time); + } break; case state::pending_hide: @@ -161,15 +170,21 @@ namespace mirage { return current_widget_; } + void tooltip_manager::set_context_provider(vulkan_context_provider* provider) { + context_provider_ = provider; + } + void tooltip_manager::do_show() { if (!pending_widget_) { current_state_ = state::hidden; return; } - // 创建tooltip窗口(如果不存在) - if (!tooltip_window_) { - tooltip_window_ = std::make_unique(); + if (!context_provider_ || !context_provider_->is_initialized()) { + std::cerr << "[tooltip_manager] Context provider not available" << std::endl; + current_state_ = state::hidden; + pending_widget_ = nullptr; + return; } // 创建tooltip内容 @@ -183,9 +198,38 @@ namespace mirage { // 包装内容(添加背景和内边距) auto wrapped_content = create_default_wrapper(content); - // 设置到tooltip窗口并显示 - tooltip_window_->set_content(wrapped_content); - tooltip_window_->show_near(pending_widget_, tooltip_position::bottom); + // 测量内容大小 + auto content_size = wrapped_content->measure(vec2f_t(800.0f, 600.0f)); + int width = static_cast(content_size.x()); + int height = static_cast(content_size.y()); + + // 确保宽高有效 + if (width <= 0) width = 100; + if (height <= 0) height = 50; + + // 创建或调整tooltip渲染窗口 + if (!tooltip_render_window_) { + if (!create_tooltip_render_window(width, height)) { + std::cerr << "[tooltip_manager] Failed to create tooltip render window" << std::endl; + current_state_ = state::hidden; + pending_widget_ = nullptr; + return; + } + } + else { + // 调整窗口大小 + tooltip_render_window_->resize(static_cast(width), static_cast(height)); + } + + // 设置tooltip内容作为root widget + tooltip_render_window_->set_root_widget(wrapped_content); + + // 计算相对于锚点的位置 + auto [x, y] = calculate_position_near_anchor(pending_widget_, tooltip_position::bottom, width, height); + + // 设置窗口位置并显示 + tooltip_render_window_->set_position(x, y); + tooltip_render_window_->show(); // 更新状态 current_state_ = state::showing; @@ -195,8 +239,8 @@ namespace mirage { } void tooltip_manager::do_hide() { - if (tooltip_window_) { - tooltip_window_->hide(); + if (tooltip_render_window_) { + tooltip_render_window_->hide(); } current_state_ = state::hidden; @@ -204,6 +248,104 @@ namespace mirage { delay_timer_ = 0.0f; } + auto tooltip_manager::create_tooltip_render_window(int width, int height) -> bool { + if (!context_provider_) { + return false; + } + + // 创建tooltip窗口配置 + // 注意:tooltip窗口现在也使用多线程架构,但作为辅助窗口类型 + render_window_config config; + config.title = "Tooltip"; + config.width = static_cast(width); + config.height = static_cast(height); + config.resizable = false; + config.vsync = false; + config.triple_buffering = false; + config.adaptive_sync = false; + config.decorated = false; // 无边框 + config.floating = true; // 浮动窗口 + config.transparent = true; // 透明 + config.focused_on_show = false; // 不获取焦点 + config.window_type = render_window_type::auxiliary; // 辅助窗口类型 + + tooltip_render_window_ = std::make_unique(*context_provider_); + + if (!tooltip_render_window_->initialize(config)) { + std::cerr << "[tooltip_manager] Failed to initialize tooltip render window" << std::endl; + tooltip_render_window_.reset(); + return false; + } + + // 初始隐藏窗口 + tooltip_render_window_->hide(); + + return true; + } + + auto tooltip_manager::calculate_position_near_anchor( + const widget_base* anchor, + tooltip_position pos, + int tooltip_width, + int tooltip_height + ) const -> std::pair { + // 获取锚点控件的窗口内位置和大小 + auto anchor_global_pos = anchor->get_global_position(); + auto anchor_size = anchor->get_size(); + + int anchor_x = static_cast(anchor_global_pos.x()); + int anchor_y = static_cast(anchor_global_pos.y()); + int anchor_width = static_cast(anchor_size.x()); + int anchor_height = static_cast(anchor_size.y()); + + // 获取锚点所在窗口的屏幕位置,转换为屏幕坐标 + if (auto* anchor_window = anchor->get_window()) { + auto [window_x, window_y] = anchor_window->get_position(); + anchor_x += window_x; + anchor_y += window_y; + } + + // 默认偏移量 + const int offset = config_.offset_from_anchor; + + int x = 0; + int y = 0; + + switch (pos) { + case tooltip_position::top: + // 在锚点上方,水平居中 + x = anchor_x + (anchor_width - tooltip_width) / 2; + y = anchor_y - tooltip_height - offset; + break; + + case tooltip_position::bottom: + // 在锚点下方,水平居中 + x = anchor_x + (anchor_width - tooltip_width) / 2; + y = anchor_y + anchor_height + offset; + break; + + case tooltip_position::left: + // 在锚点左侧,垂直居中 + x = anchor_x - tooltip_width - offset; + y = anchor_y + (anchor_height - tooltip_height) / 2; + break; + + case tooltip_position::right: + // 在锚点右侧,垂直居中 + x = anchor_x + anchor_width + offset; + y = anchor_y + (anchor_height - tooltip_height) / 2; + break; + + case tooltip_position::cursor: + // 跟随鼠标位置(使用锚点位置作为鼠标位置的近似) + x = anchor_x + offset; + y = anchor_y + offset; + break; + } + + return {x, y}; + } + auto tooltip_manager::create_tooltip_content(widget_base* widget) -> std::shared_ptr { if (!widget || !widget->has_tooltip()) { return nullptr; @@ -298,8 +440,27 @@ namespace mirage { auto tooltip_manager::create_text_widget(std::string_view text) -> std::shared_ptr { auto text_widget = std::make_shared(text); + text_widget->font_id(config_.default_font_id); text_widget->font_size(config_.default_font_size); text_widget->text_color(color::white()); + + // 预先请求字形生成,确保tooltip显示时字形已经准备好 + if (context_provider_) { + auto* glyph_cache = context_provider_->get_glyph_cache(); + if (glyph_cache && config_.default_font_id != 0) { + // 转换UTF-8文本为UTF-32 + auto utf32_text = utf8_to_utf32(text); + // 预先请求所有字形 + for (char32_t codepoint : utf32_text) { + (void)glyph_cache->get_or_request_glyph( + config_.default_font_id, + codepoint, + config_.default_font_size + ); + } + } + } + return text_widget; } } // namespace mirage diff --git a/src/ui/tooltip/tooltip_manager.h b/src/ui/tooltip/tooltip_manager.h index 6e27b5b..48790da 100644 --- a/src/ui/tooltip/tooltip_manager.h +++ b/src/ui/tooltip/tooltip_manager.h @@ -8,10 +8,12 @@ namespace mirage { // 前向声明 class widget_base; - class tooltip_window_widget; + class render_window; + class vulkan_context_provider; /// @brief Tooltip 全局管理器 /// 负责管理 tooltip 的显示/隐藏时机、延迟控制、窗口生命周期 + /// 使用 render_window 来管理 tooltip 窗口的渲染 class tooltip_manager { public: /// @brief 构造函数 @@ -29,6 +31,12 @@ namespace mirage { tooltip_manager(tooltip_manager&&) noexcept; auto operator=(tooltip_manager&&) noexcept -> tooltip_manager&; + // ========== 上下文设置 ========== + + /// @brief 设置 Vulkan 上下文提供者 + /// @param provider Vulkan 上下文提供者指针 + void set_context_provider(vulkan_context_provider* provider); + // ========== 显示控制 ========== /// @brief 请求显示控件的 tooltip(带延迟) @@ -79,7 +87,8 @@ namespace mirage { widget_base* current_widget_{nullptr}; ///< 当前显示 tooltip 的控件 float delay_timer_{0.0f}; ///< 延迟计时器(毫秒) - std::unique_ptr tooltip_window_; + vulkan_context_provider* context_provider_{nullptr}; ///< Vulkan 上下文提供者 + std::unique_ptr tooltip_render_window_; ///< tooltip 渲染窗口 /// @brief 执行实际的显示操作 void do_show(); @@ -87,6 +96,12 @@ namespace mirage { /// @brief 执行实际的隐藏操作 void do_hide(); + /// @brief 创建 tooltip 渲染窗口 + /// @param width 窗口宽度 + /// @param height 窗口高度 + /// @return 是否成功创建 + auto create_tooltip_render_window(int width, int height) -> bool; + /// @brief 为 widget 创建 tooltip 内容 /// @param widget 目标控件 /// @return tooltip 内容控件 @@ -101,5 +116,18 @@ namespace mirage { /// @param text tooltip 文本 /// @return text_widget auto create_text_widget(std::string_view text) -> std::shared_ptr; + + /// @brief 计算相对于锚点控件的 tooltip 位置 + /// @param anchor 锚点控件 + /// @param pos tooltip 显示位置 + /// @param tooltip_width tooltip 宽度 + /// @param tooltip_height tooltip 高度 + /// @return 屏幕坐标 (x, y) + auto calculate_position_near_anchor( + const widget_base* anchor, + tooltip_position pos, + int tooltip_width, + int tooltip_height + ) const -> std::pair; }; } // namespace mirage diff --git a/src/ui/tooltip/tooltip_types.h b/src/ui/tooltip/tooltip_types.h index 85d1bd6..cd2e813 100644 --- a/src/ui/tooltip/tooltip_types.h +++ b/src/ui/tooltip/tooltip_types.h @@ -12,12 +12,13 @@ namespace mirage { /// @brief Tooltip 配置 struct tooltip_config { - float show_delay_ms = 500.0f; // 显示延迟(毫秒) - float hide_delay_ms = 100.0f; // 隐藏延迟(毫秒) - float fade_duration_ms = 150.0f; // 淡入淡出时长 - float default_font_size = 12.0f; // 默认字体大小 - float corner_radius = 4.0f; // 圆角半径 - float padding = 8.0f; // 内边距 - int offset_from_anchor = 4; // 距离锚点控件的偏移 + float show_delay_ms = 500.0f; // 显示延迟(毫秒) + float hide_delay_ms = 100.0f; // 隐藏延迟(毫秒) + float fade_duration_ms = 150.0f; // 淡入淡出时长 + float default_font_size = 12.0f; // 默认字体大小 + uint32_t default_font_id = 0; // 默认字体ID(0表示使用系统默认字体) + float corner_radius = 4.0f; // 圆角半径 + float padding = 8.0f; // 内边距 + int offset_from_anchor = 4; // 距离锚点控件的偏移 }; } // namespace mirage diff --git a/src/ui/window/render_window.cpp b/src/ui/window/render_window.cpp index 9fbcfd8..6ed4942 100644 --- a/src/ui/window/render_window.cpp +++ b/src/ui/window/render_window.cpp @@ -53,12 +53,27 @@ namespace mirage { .width = static_cast(config.width), .height = static_cast(config.height), .mode = window_mode::WINDOWED, - .flags = window_flag::VISIBLE | window_flag::DECORATED | window_flag::FOCUSED + .flags = window_flag::NONE }; + // 根据配置设置窗口标志 + if (config.decorated) { + window_info.flags = window_info.flags | window_flag::DECORATED; + } + if (config.focused_on_show) { + window_info.flags = window_info.flags | window_flag::FOCUSED; + } + if (config.floating) { + window_info.flags = window_info.flags | window_flag::FLOATING; + } + if (config.transparent) { + window_info.flags = window_info.flags | window_flag::TRANSPARENT; + } if (config.resizable) { window_info.flags = window_info.flags | window_flag::RESIZABLE; } + // 默认可见 + window_info.flags = window_info.flags | window_flag::VISIBLE; auto window_result = create_window(window_info); if (!window_result.has_value()) { @@ -110,7 +125,7 @@ namespace mirage { // 使用窗口ID注册,如果还没有分配ID则使用临时ID window_id reg_id = (id_ != INVALID_WINDOW_ID) ? id_ : reinterpret_cast(this); - if (!pipeline->register_window(reg_id, surface_, current_width_, current_height_)) { + if (!pipeline->register_window(reg_id, surface_, current_width_, current_height_, config.window_type)) { std::cerr << "[render_window] Failed to register window with render pipeline" << std::endl; return false; } @@ -128,7 +143,8 @@ namespace mirage { &event_router_->get_ime_manager() ); - tooltip_manager_ = std::make_unique(); + tooltip_manager_ = std::make_unique(config.tooltip_cfg); + tooltip_manager_->set_context_provider(&context_provider_); event_router_->set_tooltip_manager(tooltip_manager_.get()); // 创建主窗口 widget @@ -148,9 +164,8 @@ namespace mirage { // 配置线程协调器 coordinator_config coord_config{}; - coord_config.enable_multithreading = config.enable_multithreading; - coord_config.max_frames_in_flight = 2; - coord_config.adaptive_sync = config.adaptive_sync; + coord_config.max_frames_in_flight = 2; + coord_config.adaptive_sync = config.adaptive_sync; coord_config.render_cfg.vsync_enabled = config.vsync; coord_config.render_cfg.triple_buffering = config.triple_buffering; @@ -165,6 +180,9 @@ namespace mirage { coord_config.render_cfg.target_fps = config.target_fps; } + // 设置窗口ID关联(复用之前定义的 reg_id) + coordinator_->set_window_id(reg_id); + coordinator_->start(coord_config); // ======================================================================== @@ -210,8 +228,8 @@ namespace mirage { // 4. 提交渲染 render(); - // 5. 处理完成的帧(多线程模式) - if (coordinator_ && config_.enable_multithreading) { + // 5. 处理完成的帧 + if (coordinator_) { coordinator_->process_completed_frames(); } } @@ -223,11 +241,20 @@ namespace mirage { } void render_window::update(float delta_time) { - // 处理异步字形生成 + // 检查是否有新字形生成完成(使用版本号机制) + // process_completed_glyphs() 由 vulkan_context_provider::tick() 统一调用 + // 这里只需要检查版本号变化来决定是否需要重绘 auto* glyph_cache = context_provider_.get_glyph_cache(); - if (glyph_cache) { - bool has_new_glyphs = glyph_cache->process_completed_glyphs(); - if (has_new_glyphs && root_widget_) { + if (glyph_cache && root_widget_) { + uint64_t current_version = glyph_cache->get_version(); + if (current_version != last_glyph_cache_version_) { + last_glyph_cache_version_ = current_version; + root_widget_->mark_render_dirty(); + } + + // 如果有待处理的字形任务,持续标记为脏 + // 这确保在字形生成完成后能够重新渲染 + if (glyph_cache->has_pending_tasks()) { root_widget_->mark_render_dirty(); } } @@ -259,12 +286,8 @@ namespace mirage { static_cast(current_height_) ); - if (config_.enable_multithreading) { - coordinator_->submit_frame(root_widget_.get(), viewport_size); - } - else { - coordinator_->process_frame_single_threaded(root_widget_.get(), viewport_size); - } + // 统一使用多线程模式提交帧 + coordinator_->submit_frame(root_widget_.get(), viewport_size); } void render_window::set_root_widget(std::shared_ptr root) { @@ -298,6 +321,44 @@ namespace mirage { return {current_width_, current_height_}; } + void render_window::set_position(int x, int y) { + if (window_) { + (void)window_->set_position(x, y); + } + } + + std::pair render_window::get_position() const { + if (window_) { + return window_->get_position(); + } + return {0, 0}; + } + + void render_window::resize(uint32_t width, uint32_t height) { + if (window_) { + (void)window_->set_size(static_cast(width), static_cast(height)); + } + } + + void render_window::show() { + if (window_) { + window_->show(); + } + } + + void render_window::hide() { + if (window_) { + window_->hide(); + } + } + + bool render_window::is_visible() const { + if (window_) { + return window_->is_visible(); + } + return false; + } + void render_window::set_update_callback(update_callback_t callback) { update_callback_ = std::move(callback); } @@ -310,6 +371,12 @@ namespace mirage { close_callback_ = std::move(callback); } + void render_window::set_tooltip_config(const tooltip_config& config) { + if (tooltip_manager_) { + tooltip_manager_->set_config(config); + } + } + void render_window::handle_event(const event& e) { switch (e.type) { case event_type::WINDOW_CLOSE: diff --git a/src/ui/window/render_window.h b/src/ui/window/render_window.h index 10abaad..ab30b84 100644 --- a/src/ui/window/render_window.h +++ b/src/ui/window/render_window.h @@ -17,7 +17,9 @@ #include "ui/window/window_id.h" #include "ui/window/window_type.h" #include "ui/window/window_common.h" +#include "ui/tooltip/tooltip_types.h" #include "render/vulkan/vulkan_common.h" +#include "render/pipeline/render_pipeline.h" #include "core/types.h" namespace mirage { @@ -36,15 +38,26 @@ namespace mirage { /// @brief 渲染窗口配置 struct render_window_config { - std::string title = "Mirage Window"; - uint32_t width = 1280; - uint32_t height = 720; - bool resizable = true; - bool vsync = true; - bool enable_multithreading = true; - bool triple_buffering = true; - bool adaptive_sync = false; + std::string title = "Mirage Window"; + uint32_t width = 1280; + uint32_t height = 720; + bool resizable = true; + bool vsync = true; + bool triple_buffering = true; + bool adaptive_sync = false; std::optional target_fps; + + // tooltip 窗口特有配置 + bool decorated = true; ///< 是否有边框装饰 + bool floating = false; ///< 是否浮动在其他窗口之上 + bool transparent = false; ///< 是否透明 + bool focused_on_show = true; ///< 显示时是否获取焦点 + + // 渲染窗口类型(用于区分主窗口和辅助窗口) + render_window_type window_type = render_window_type::primary; + + // tooltip 配置 + tooltip_config tooltip_cfg; }; /// @brief 渲染窗口状态 @@ -163,6 +176,37 @@ namespace mirage { /// @brief 获取 tooltip 管理器 [[nodiscard]] tooltip_manager* get_tooltip_manager() const { return tooltip_manager_.get(); } + /// @brief 设置 tooltip 配置 + /// @param config tooltip 配置 + void set_tooltip_config(const tooltip_config& config); + + // ========================================================================= + // 窗口控制(用于 tooltip 等浮动窗口) + // ========================================================================= + + /// @brief 设置窗口位置 + /// @param x 屏幕 X 坐标 + /// @param y 屏幕 Y 坐标 + void set_position(int x, int y); + + /// @brief 获取窗口位置 + /// @return 窗口位置 (x, y) + [[nodiscard]] std::pair get_position() const; + + /// @brief 调整窗口大小 + /// @param width 新宽度 + /// @param height 新高度 + void resize(uint32_t width, uint32_t height); + + /// @brief 显示窗口 + void show(); + + /// @brief 隐藏窗口 + void hide(); + + /// @brief 检查窗口是否可见 + [[nodiscard]] bool is_visible() const; + // ========================================================================= // 回调设置 // ========================================================================= @@ -224,6 +268,9 @@ namespace mirage { double last_mouse_x_ = 0.0; double last_mouse_y_ = 0.0; + // 字形缓存版本号(用于检测新字形生成完成) + uint64_t last_glyph_cache_version_ = 0; + // 回调 update_callback_t update_callback_; resize_callback_t resize_callback_;