diff --git a/docs/DESIGN_RENDER_PIPELINE_REFACTOR_2025.md b/docs/DESIGN_RENDER_PIPELINE_REFACTOR_2025.md deleted file mode 100644 index f47f9f3..0000000 --- a/docs/DESIGN_RENDER_PIPELINE_REFACTOR_2025.md +++ /dev/null @@ -1,1316 +0,0 @@ - -# 零开销渲染管线状态管理框架设计 - -## Zero-Overhead Render Pipeline State Management Framework - -**版本**: 1.0 -**日期**: 2025-11-30 -**状态**: 设计阶段 - ---- - -## 目录 - -1. [问题分析](#1-问题分析) -2. [设计哲学](#2-设计哲学) -3. [状态转换图](#3-状态转换图) -4. [核心类型定义](#4-核心类型定义) -5. [API 使用示例](#5-api-使用示例) -6. [编译时检查示例](#6-编译时检查示例) -7. [与现有代码的集成策略](#7-与现有代码的集成策略) -8. [性能证明](#8-性能证明) -9. [附录](#附录) - ---- - -## 1. 问题分析 - -### 1.1 现有代码结构问题 - -通过分析 [`render_pipeline.cpp`](../src/render/pipeline/render_pipeline.cpp)、[`mask_renderer.cpp`](../src/render/pipeline/mask_renderer.cpp) 和 [`post_effect_applicator.cpp`](../src/render/pipeline/post_effect_applicator.cpp),发现以下核心问题: - -#### 问题 1: RenderPass 控制权分散 - -``` -render_pipeline::render_segments() -> 管理主要 render pass -mask_renderer::begin_mask() -> 开始遮罩目标的 render pass (line 352) -mask_renderer::end_mask() -> 开始父目标的 render pass (line 377) -post_effect_applicator::apply_blur() -> 管理 temp_target 的 render pass (line 548, 602) -``` - -#### 问题 2: 隐式状态依赖 - -在 [`render_pipeline.cpp:475-506`](../src/render/pipeline/render_pipeline.cpp:475) 中: - -```cpp -else if (segment.type == segment_type::mask_end) { - // 结束当前 render pass(遮罩临时目标的) - if (render_pass_active) { - active_target->end_render(cmd, true); - render_pass_active = false; - active_target = nullptr; - } - - // end_mask 会开始父目标的 render pass 并绘制遮罩四边形 - mask_->end_mask(cmd); - - // end_mask 会开始父目标的 render pass - // 我们需要跟踪这个状态 - if (mask_->is_in_mask()) { - active_target = mask_->get_current_target(); - } else { - active_target = offscreen_.get(); - } - render_pass_active = true; // ← 假设 end_mask 开始了 render pass -} -``` - -**问题**: `render_pass_active = true` 是基于 `end_mask` 内部行为的**隐式假设**。 - -#### 问题 3: 运行时状态跟踪 - -[`offscreen_target.h:17-23`](../src/render/pipeline/offscreen_target.h:17) 使用运行时枚举: - -```cpp -enum class state { - undefined, - render_target, - shader_read, - transfer_src, - transfer_dst -}; -``` - -这种设计无法在编译时捕获非法的状态转换。 - -### 1.2 理想状态 - -- **单一控制点**: 所有 RenderPass 由统一的控制器管理 -- **编译时验证**: 非法状态转换在编译期报错 -- **显式所有权**: RenderPass 的生命周期通过类型系统显式表达 -- **零运行时开销**: 不使用虚函数、RTTI 或异常 - ---- - -## 2. 设计哲学 - -### 2.1 为什么选择 Typestate + deducing this - -#### Typestate 模式优势 - -Typestate 将对象状态编码到类型系统中,使编译器能够验证状态转换的正确性: - -```cpp -// 传统方式:运行时检查 -class RenderPass { - bool active_ = false; -public: - void begin() { - if (active_) throw std::runtime_error("Already active"); // 运行时错误 - active_ = true; - } -}; - -// Typestate 方式:编译时检查 -template class render_pass; - -template<> class render_pass { -public: - [[nodiscard]] auto begin() && -> render_pass; // 只能从 idle 转换 -}; - -template<> class render_pass { -public: - [[nodiscard]] auto end() && -> render_pass; // 只能从 active 转换 -}; -``` - -#### deducing this (C++23) 的简化作用 - -传统 CRTP 需要繁琐的模板继承: - -```cpp -// 传统 CRTP -template -class base { - auto begin() -> ... { - return static_cast(this)->do_begin(); - } -}; -``` - -deducing this 消除了这种复杂性: - -```cpp -// C++23 deducing this -class render_context { - template - auto begin_pass(this Self&& self, target& t) -> pass_guard<...> { - // Self 自动推导为实际类型 - } -}; -``` - -### 2.2 核心设计原则 - -1. **Move-only 语义**: 状态对象只能移动,防止复制导致的状态混乱 -2. **[[nodiscard]]**: 强制调用者处理返回值,防止忽略状态转换结果 -3. **RAII**: 利用析构函数确保资源正确释放 -4. **constexpr/consteval**: 尽可能在编译时进行验证 - ---- - -## 3. 状态转换图 - -### 3.1 RenderPass 状态机 - -``` - ┌─────────────────────────────────────────────────────┐ - │ Frame Level │ - └─────────────────────────────────────────────────────┘ - │ - ▼ - ┌─────────────────────────────────────────────────────────────────────────────────┐ - │ frame_context │ - │ │ - │ • 持有: command_buffer, frame_index, resources... │ - │ • 可执行: begin_offscreen_pass(), begin_swapchain_pass() │ - └─────────────────────────────────────────────────────────────────────────────────┘ - │ │ - begin_offscreen_pass() begin_swapchain_pass() - │ │ - ▼ ▼ - ┌───────────────────────────────────────┐ ┌───────────────────────────────────┐ - │ frame_context │ │ frame_context│ - │ │ │ │ - │ • 持有: active_target* │ │ • 持有: swapchain_framebuffer │ - │ • 可执行: │ │ • 可执行: │ - │ - render_geometry() │ │ - blit() │ - │ - render_image() │ │ - end_swapchain_pass() │ - │ - end_offscreen_pass() │ └───────────────────────────────────┘ - │ - begin_nested_pass() │ │ - │ - apply_post_effect() │ end_swapchain_pass() - └───────────────────────────────────────┘ │ - │ ▼ - │ ┌───────────────────────────────────┐ - end_offscreen_pass() │ frame_context │ - apply_post_effect() │ │ - │ │ • 帧渲染完成 │ - │ │ • 可提交到 GPU │ - ▼ └───────────────────────────────────┘ - ┌───────────────────────────────────────┐ - │ frame_context │ - │ │ - │ (回到空闲状态,可开始新的 pass) │ - └───────────────────────────────────────┘ -``` - -### 3.2 嵌套遮罩状态机 - -``` - ┌─────────────────────────────────────────────────────────────────────────────────┐ - │ frame_context │ - │ (深度 = 0, 主离屏目标) │ - └─────────────────────────────────────────────────────────────────────────────────┘ - │ - begin_nested_pass(mask_target_0) - │ - ▼ - ┌─────────────────────────────────────────────────────────────────────────────────┐ - │ nested_pass_context<1> │ - │ │ - │ • 深度: 1 │ - │ • 当前目标: mask_target_0 │ - │ • 父目标: 主离屏目标 │ - │ • 可执行: render_geometry(), begin_nested_pass(), end_nested_pass() │ - └─────────────────────────────────────────────────────────────────────────────────┘ - │ - begin_nested_pass(mask_target_1) - │ - ▼ - ┌─────────────────────────────────────────────────────────────────────────────────┐ - │ nested_pass_context<2> │ - │ │ - │ • 深度: 2 │ - │ • 当前目标: mask_target_1 │ - │ • 父目标: mask_target_0 │ - └─────────────────────────────────────────────────────────────────────────────────┘ - │ - end_nested_pass() - │ - ▼ - ┌─────────────────────────────────────────────────────────────────────────────────┐ - │ nested_pass_context<1> │ - │ │ - │ (返回到深度 1,遮罩合成完成) │ - └─────────────────────────────────────────────────────────────────────────────────┘ - │ - end_nested_pass() - │ - ▼ - ┌─────────────────────────────────────────────────────────────────────────────────┐ - │ frame_context │ - │ (回到主离屏目标) │ - └─────────────────────────────────────────────────────────────────────────────────┘ -``` - -### 3.3 合法状态转换表 - -| 当前状态 | 可转换到 | 条件 | -|---------|---------|------| -| `idle_tag` | `offscreen_pass_tag` | 调用 `begin_offscreen_pass()` | -| `idle_tag` | `swapchain_pass_tag` | 调用 `begin_swapchain_pass()` | -| `offscreen_pass_tag` | `idle_tag` | 调用 `end_offscreen_pass()` 或 `apply_post_effect()` | -| `offscreen_pass_tag` | `nested_pass_context` | 调用 `begin_nested_pass()` | -| `nested_pass_context` | `nested_pass_context` | 调用 `end_nested_pass()` (N > 1) | -| `nested_pass_context<1>` | `offscreen_pass_tag` | 调用 `end_nested_pass()` | -| `swapchain_pass_tag` | `completed_tag` | 调用 `end_swapchain_pass()` | - ---- - -## 4. 核心类型定义 - -### 4.1 状态标签 - -```cpp -// file: src/render/pipeline/pass_state_tags.h -#pragma once - -namespace mirage::pass_state { - -/// 编译时状态标签 -struct idle_tag {}; -struct offscreen_pass_tag {}; -struct swapchain_pass_tag {}; -struct completed_tag {}; - -/// 嵌套深度标签 (用于遮罩) -template -struct nested_depth_tag { - static constexpr std::size_t depth = Depth; -}; - -/// 编译时状态验证 -template -struct is_valid_transition : std::false_type {}; - -// 定义合法转换 -template<> -struct is_valid_transition : std::true_type {}; - -template<> -struct is_valid_transition : std::true_type {}; - -template<> -struct is_valid_transition : std::true_type {}; - -template<> -struct is_valid_transition : std::true_type {}; - -template -struct is_valid_transition> : std::true_type {}; - -template requires (N > 1) -struct is_valid_transition, nested_depth_tag> : std::true_type {}; - -template<> -struct is_valid_transition, offscreen_pass_tag> : std::true_type {}; - -template -inline constexpr bool is_valid_transition_v = is_valid_transition::value; - -/// 编译时转换验证 -template -consteval auto validate_transition() -> bool { - static_assert(is_valid_transition_v, - "Invalid state transition: this transition is not allowed"); - return true; -} - -} // namespace mirage::pass_state -``` - -### 4.2 渲染目标 Concept - -```cpp -// file: src/render/pipeline/render_target_concept.h -#pragma once - -#include "vulkan/vulkan_common.h" -#include - -namespace mirage { - -/// 渲染目标 Concept - 定义渲染目标必须满足的接口 -template -concept RenderTarget = requires(T t, vk::CommandBuffer cmd, bool flag) { - /// 开始渲染到此目标 - { t.begin_render(cmd, flag) } -> std::same_as; - - /// 结束渲染 - { t.end_render(cmd, flag) } -> std::same_as; - - /// 状态转换 - { t.transition_to(cmd, std::declval()) } -> std::same_as; - - /// 获取 RenderPass - { t.render_pass(flag) } -> std::same_as; - - /// 获取 ImageView - { t.view() } -> std::same_as; - - /// 获取 Extent - { t.extent() } -> std::same_as; -}; - -/// 可采样目标 Concept -template -concept SampleableTarget = RenderTarget && requires(T t) { - /// 获取采样器 - { t.sampler() } -> std::same_as; -}; - -/// 批次渲染器 Concept -template -concept BatchRenderer = requires(R r, vk::CommandBuffer cmd, const draw_batch& batch) { - { r.render(cmd, batch) } -> std::same_as; -}; - -} // namespace mirage -``` - -### 4.3 帧上下文核心类型 - -```cpp -// file: src/render/pipeline/frame_context_v2.h -#pragma once - -#include "pass_state_tags.h" -#include "render_target_concept.h" -#include "offscreen_target.h" -#include "types.h" -#include -#include - -namespace mirage { - -// 前向声明 -class geometry_renderer; -class image_renderer; -class mask_renderer; -class post_effect_applicator; - -/// 渲染错误类型 -enum class render_error { - invalid_target, - pass_not_active, - buffer_overflow, - descriptor_allocation_failed -}; - -/// 帧资源引用 (不拥有资源) -struct frame_resources { - vk::CommandBuffer command_buffer; - uint32_t frame_index; - uint32_t image_index; - vk::Framebuffer swapchain_framebuffer; - vk::RenderPass swapchain_render_pass; - vk::Extent2D extent; - - // 渲染器引用 - geometry_renderer* geometry; - image_renderer* imager; - mask_renderer* mask; - post_effect_applicator* effects; - - // 渲染目标 - offscreen_target* main_offscreen; -}; - -// ============================================================================ -// 主帧上下文模板 -// ============================================================================ - -template -class frame_context; - -/// Idle 状态的帧上下文 -template<> -class frame_context { -public: - /// 从帧资源构造 - explicit frame_context(frame_resources res) noexcept - : res_(std::move(res)) {} - - /// Move-only - frame_context(const frame_context&) = delete; - frame_context& operator=(const frame_context&) = delete; - frame_context(frame_context&&) noexcept = default; - frame_context& operator=(frame_context&&) noexcept = default; - - /// 开始离屏渲染 Pass - /// @param clear 是否清除目标 - /// @return 转换后的上下文 - [[nodiscard]] - auto begin_offscreen_pass(this frame_context&& self, bool clear = true) - -> frame_context; - - /// 开始交换链渲染 Pass - [[nodiscard]] - auto begin_swapchain_pass(this frame_context&& self) - -> frame_context; - - /// 访问帧资源 - [[nodiscard]] auto resources() const noexcept -> const frame_resources& { - return res_; - } - -private: - frame_resources res_; -}; - -/// Offscreen Pass 状态的帧上下文 -template<> -class frame_context { -public: - /// 私有构造,只能通过状态转换创建 - frame_context(frame_resources res, offscreen_target* active_target) noexcept - : res_(std::move(res)) - , active_target_(active_target) {} - - /// Move-only - frame_context(const frame_context&) = delete; - frame_context& operator=(const frame_context&) = delete; - frame_context(frame_context&&) noexcept = default; - frame_context& operator=(frame_context&&) noexcept = default; - - /// 析构时自动结束 render pass (如果未手动结束) - ~frame_context() { - if (active_target_ && pass_active_) { - active_target_->end_render(res_.command_buffer, true); - } - } - - /// 渲染几何批次 - template - auto render_batch(this frame_context& self, R& renderer, const draw_batch& batch) - -> std::expected { - if (!self.pass_active_) { - return std::unexpected(render_error::pass_not_active); - } - renderer.render(self.res_.command_buffer, batch); - return {}; - } - - /// 开始嵌套渲染 Pass (用于遮罩) - [[nodiscard]] - auto begin_nested_pass(this frame_context&& self, offscreen_target& nested_target, bool clear = true) - -> nested_pass_context<1>; - - /// 结束离屏渲染 Pass - [[nodiscard]] - auto end_offscreen_pass(this frame_context&& self) - -> frame_context { - // 编译时验证转换合法性 - static_assert( - pass_state::is_valid_transition_v< - pass_state::offscreen_pass_tag, - pass_state::idle_tag - > - ); - - if (self.active_target_ && self.pass_active_) { - self.active_target_->end_render(self.res_.command_buffer, true); - self.pass_active_ = false; - } - - return frame_context(std::move(self.res_)); - } - - /// 应用后效 (结束当前 pass,应用效果,不返回到 pass 状态) - [[nodiscard]] - auto apply_post_effect(this frame_context&& self, const post_effect_command& effect, float time) - -> frame_context; - - /// 获取当前活动目标 - [[nodiscard]] auto active_target() const noexcept -> offscreen_target* { - return active_target_; - } - - /// 获取帧资源 - [[nodiscard]] auto resources() const noexcept -> const frame_resources& { - return res_; - } - -private: - frame_resources res_; - offscreen_target* active_target_ = nullptr; - bool pass_active_ = true; - - friend class frame_context; -}; - -/// Swapchain Pass 状态的帧上下文 -template<> -class frame_context { -public: - frame_context(frame_resources res) noexcept - : res_(std::move(res)) {} - - /// Move-only - frame_context(const frame_context&) = delete; - frame_context& operator=(const frame_context&) = delete; - frame_context(frame_context&&) noexcept = default; - frame_context& operator=(frame_context&&) noexcept = default; - - ~frame_context() { - if (pass_active_) { - res_.command_buffer.endRenderPass(); - } - } - - /// 执行 Blit 操作 - auto blit_offscreen(this frame_context& self, - vk::Pipeline blit_pipeline, - vk::PipelineLayout blit_layout, - vk::DescriptorSet blit_descriptor) -> void { - auto& cmd = self.res_.command_buffer; - - cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, blit_pipeline); - - vk::Viewport viewport{ - 0.0f, 0.0f, - static_cast(self.res_.extent.width), - static_cast(self.res_.extent.height), - 0.0f, 1.0f - }; - cmd.setViewport(0, 1, &viewport); - - vk::Rect2D scissor{{0, 0}, self.res_.extent}; - cmd.setScissor(0, 1, &scissor); - - cmd.bindDescriptorSets( - vk::PipelineBindPoint::eGraphics, - blit_layout, 0, 1, &blit_descriptor, 0, nullptr - ); - - cmd.draw(3, 1, 0, 0); // 全屏三角形 - } - - /// 结束交换链 Pass - [[nodiscard]] - auto end_swapchain_pass(this frame_context&& self) - -> frame_context { - static_assert( - pass_state::is_valid_transition_v< - pass_state::swapchain_pass_tag, - pass_state::completed_tag - > - ); - - self.res_.command_buffer.endRenderPass(); - self.pass_active_ = false; - - return frame_context(std::move(self.res_)); - } - -private: - frame_resources res_; - bool pass_active_ = true; -}; - -/// Completed 状态的帧上下文 -template<> -class frame_context { -public: - explicit frame_context(frame_resources res) noexcept - : res_(std::move(res)) {} - - frame_context(const frame_context&) = delete; - frame_context& operator=(const frame_context&) = delete; - frame_context(frame_context&&) noexcept = default; - frame_context& operator=(frame_context&&) noexcept = default; - - /// 获取帧资源用于提交 - [[nodiscard]] auto resources() const noexcept -> const frame_resources& { - return res_; - } - - /// 获取命令缓冲用于提交 - [[nodiscard]] auto command_buffer() const noexcept -> vk::CommandBuffer { - return res_.command_buffer; - } - - [[nodiscard]] auto frame_index() const noexcept -> uint32_t { - return res_.frame_index; - } - -private: - frame_resources res_; -}; - -} // namespace mirage -``` - -### 4.4 嵌套 Pass 上下文 (用于遮罩) - -```cpp -// file: src/render/pipeline/nested_pass_context.h -#pragma once - -#include "frame_context_v2.h" -#include - -namespace mirage { - -/// 嵌套 Pass 上下文 - 用于遮罩渲染 -/// @tparam Depth 嵌套深度 (1 = 第一层遮罩, 2 = 嵌套遮罩, ...) -template -class nested_pass_context { - static_assert(Depth > 0, "Nested depth must be positive"); - -public: - static constexpr std::size_t depth = Depth; - using depth_tag = pass_state::nested_depth_tag; - - /// 构造 (仅由 frame_context 或更低深度的 nested_pass_context 创建) - nested_pass_context( - frame_resources res, - offscreen_target* current_target, - offscreen_target* parent_target - ) noexcept - : res_(std::move(res)) - , current_target_(current_target) - , parent_target_(parent_target) {} - - /// Move-only - nested_pass_context(const nested_pass_context&) = delete; - nested_pass_context& operator=(const nested_pass_context&) = delete; - nested_pass_context(nested_pass_context&&) noexcept = default; - nested_pass_context& operator=(nested_pass_context&&) noexcept = default; - - /// 析构时确保 render pass 已结束 - ~nested_pass_context() { - if (current_target_ && pass_active_) { - current_target_->end_render(res_.command_buffer, true); - } - } - - /// 渲染批次到当前嵌套目标 - template - auto render_batch(this nested_pass_context& self, R& renderer, const draw_batch& batch) - -> std::expected { - if (!self.pass_active_) { - return std::unexpected(render_error::pass_not_active); - } - renderer.render(self.res_.command_buffer, batch); - return {}; - } - - /// 开始更深层的嵌套 Pass - [[nodiscard]] - auto begin_nested_pass(this nested_pass_context&& self, offscreen_target& target, bool clear = true) - -> nested_pass_context { - // 编译时验证 - static_assert( - pass_state::is_valid_transition_v< - depth_tag, - pass_state::nested_depth_tag - > - ); - - // 结束当前 pass - if (self.current_target_ && self.pass_active_) { - self.current_target_->end_render(self.res_.command_buffer, true); - self.pass_active_ = false; - } - - // 开始新的嵌套 pass - target.begin_render(self.res_.command_buffer, clear); - - return nested_pass_context( - std::move(self.res_), - &target, - self.current_target_ - ); - } - - /// 结束当前嵌套 Pass,返回父级 - /// 对于 Depth > 1: 返回 nested_pass_context - /// 对于 Depth == 1: 返回 frame_context - [[nodiscard]] - auto end_nested_pass(this nested_pass_context&& self) - -> std::conditional_t< - (Depth > 1), - nested_pass_context, - frame_context - > { - // 编译时验证 - if constexpr (Depth > 1) { - static_assert( - pass_state::is_valid_transition_v< - depth_tag, - pass_state::nested_depth_tag - > - ); - } else { - static_assert( - pass_state::is_valid_transition_v< - depth_tag, - pass_state::offscreen_pass_tag - > - ); - } - - // 结束当前 pass - if (self.current_target_ && self.pass_active_) { - self.current_target_->end_render(self.res_.command_buffer, true); - // 转换为 shader read 用于遮罩合成 - self.current_target_->transition_to( - self.res_.command_buffer, - offscreen_target::state::shader_read - ); - self.pass_active_ = false; - } - - // 开始父目标的 render pass (用于绘制遮罩四边形) - if (self.parent_target_) { - self.parent_target_->begin_render(self.res_.command_buffer, false); - } - - // 返回适当的上下文类型 - if constexpr (Depth > 1) { - return nested_pass_context( - std::move(self.res_), - self.parent_target_, - nullptr // 父级的父目标需要从栈中获取 - ); - } else { - return frame_context( - std::move(self.res_), - self.parent_target_ - ); - } - } - - /// 获取当前目标 - [[nodiscard]] auto current_target() const noexcept -> offscreen_target* { - return current_target_; - } - - /// 获取父目标 - [[nodiscard]] auto parent_target() const noexcept -> offscreen_target* { - return parent_target_; - } - -private: - frame_resources res_; - offscreen_target* current_target_ = nullptr; - offscreen_target* parent_target_ = nullptr; - bool pass_active_ = true; -}; - -} // namespace mirage -``` - ---- - -## 5. API 使用示例 - -### 5.1 完整的渲染帧流程 - -```cpp -// file: example usage in render_pipeline_v2.cpp - -auto render_pipeline_v2::render_frame(const std::vector& commands) -> bool { - // 1. 获取帧上下文 (idle 状态) - auto idle_ctx = scheduler_->begin_frame_v2(); - if (!idle_ctx) { - return false; // 需要重建 swapchain - } - - // 2. 处理命令生成渲染段 - auto segments = processor_->process(commands); - - // 3. 开始离屏渲染 - 状态转换: idle -> offscreen_pass - auto offscreen_ctx = std::move(*idle_ctx).begin_offscreen_pass(true); - - // 4. 渲染几何段 - for (const auto& segment : segments) { - if (segment.type == segment_type::geometry) { - for (const auto& batch : segment.batches) { - if (batch.texture_id.has_value()) { - offscreen_ctx.render_batch(*imager_, batch); - } else { - offscreen_ctx.render_batch(*geometry_, batch); - } - } - } - } - - // 5. 结束离屏渲染 - 状态转换: offscreen_pass -> idle - auto idle_ctx2 = std::move(offscreen_ctx).end_offscreen_pass(); - - // 6. 开始交换链渲染 - 状态转换: idle -> swapchain_pass - auto swapchain_ctx = std::move(idle_ctx2).begin_swapchain_pass(); - - // 7. Blit 离屏目标到交换链 - swapchain_ctx.blit_offscreen(blit_pipeline_, blit_layout_, blit_descriptor_); - - // 8. 结束交换链渲染 - 状态转换: swapchain_pass -> completed - auto completed_ctx = std::move(swapchain_ctx).end_swapchain_pass(); - - // 9. 提交帧 - return scheduler_->end_frame_v2(std::move(completed_ctx)); -} -``` - -### 5.2 带遮罩的渲染流程 - -```cpp -auto render_with_masks( - frame_context ctx, - const std::vector& segments, - mask_renderer& mask -) -> frame_context { - - for (const auto& segment : segments) { - switch (segment.type) { - case segment_type::geometry: - // 渲染几何到当前目标 - for (const auto& batch : segment.batches) { - ctx.render_batch(*ctx.resources().geometry, batch); - } - break; - - case segment_type::mask_begin: { - // 获取遮罩目标 - auto* mask_target = mask.acquire_render_target(); - - // 开始嵌套 pass - 状态转换: offscreen_pass -> nested<1> - auto nested_ctx = std::move(ctx).begin_nested_pass(*mask_target, true); - - // 在遮罩目标中渲染内容... - // (递归处理嵌套段) - - // 结束嵌套 pass - 状态转换: nested<1> -> offscreen_pass - ctx = std::move(nested_ctx).end_nested_pass(); - - // 绘制遮罩四边形 - mask.draw_mask_quad(ctx.resources().command_buffer, ...); - - mask.release_render_target(mask_target); - break; - } - - case segment_type::post_effect: - // 应用后效 - 状态转换: offscreen_pass -> idle - auto idle = std::move(ctx).apply_post_effect(segment.effect.value(), time); - // 重新开始离屏 pass - ctx = std::move(idle).begin_offscreen_pass(false); - break; - } - } - - return std::move(ctx).end_offscreen_pass(); -} -``` - -### 5.3 使用 RAII 保证的安全渲染 - -```cpp -// scoped_pass 自动管理 render pass 生命周期 -class scoped_pass { -public: - explicit scoped_pass(frame_context& ctx) - : ctx_(&ctx) {} - - ~scoped_pass() { - // 析构时 ctx 会自动调用 end_render - // 因为 frame_context 的析构函数会处理 - } - - // 禁止复制 - scoped_pass(const scoped_pass&) = delete; - scoped_pass& operator=(const scoped_pass&) = delete; - -private: - frame_context* ctx_; -}; - -// 使用示例 -void render_safely(frame_context idle_ctx) { - { - auto offscreen_ctx = std::move(idle_ctx).begin_offscreen_pass(true); - scoped_pass guard(offscreen_ctx); // RAII 保护 - - // 渲染代码... - // 如果这里抛出异常或提前返回,render pass 仍会正确结束 - - } // guard 析构,offscreen_ctx 析构时自动结束 render pass -} -``` - ---- - -## 6. 编译时检查示例 - -### 6.1 非法状态转换 - 编译错误 - -```cpp -// ❌ 错误:不能从 idle 直接结束离屏 pass -auto idle_ctx = scheduler_->begin_frame_v2(); -auto completed = std::move(*idle_ctx).end_offscreen_pass(); -// 编译错误:frame_context 没有 end_offscreen_pass 成员 - -// ❌ 错误:不能从 swapchain_pass 开始离屏 pass -auto swapchain_ctx = std::move(*idle_ctx).begin_swapchain_pass(); -auto offscreen_ctx = std::move(swapchain_ctx).begin_offscreen_pass(true); -// 编译错误:frame_context 没有 begin_offscreen_pass 成员 - -// ❌ 错误:不能跳过深度直接结束嵌套 -auto nested2 = std::move(nested1).begin_nested_pass(target2); -auto idle = std::move(nested2).end_offscreen_pass(); -// 编译错误:nested_pass_context<2> 没有 end_offscreen_pass 成员 -``` - -### 6.2 忘记处理返回值 - 编译警告 - -```cpp -// ⚠️ 警告:[[nodiscard]] 返回值被忽略 -auto offscreen_ctx = std::move(idle_ctx).begin_offscreen_pass(true); -offscreen_ctx.end_offscreen_pass(); // 返回值被忽略! -// 警告:ignoring return value of function declared with 'nodiscard' attribute - -// ✅ 正确用法 -auto idle_ctx2 = std::move(offscreen_ctx).end_offscreen_pass(); -``` - -### 6.3 复制 move-only 类型 - 编译错误 - -```cpp -// ❌ 错误:不能复制 frame_context -auto offscreen_ctx = std::move(idle_ctx).begin_offscreen_pass(true); -auto copy = offscreen_ctx; // 编译错误:deleted copy constructor - -// ✅ 正确用法:只能移动 -auto moved = std::move(offscreen_ctx); -``` - -### 6.4 静态断言验证 - -```cpp -// 在 frame_context 内部使用 static_assert 进行编译时验证 -template<> -class frame_context { - [[nodiscard]] - auto end_offscreen_pass(this frame_context&& self) - -> frame_context { - - // 编译时验证:offscreen_pass -> idle 是合法转换 - static_assert( - pass_state::is_valid_transition_v< - pass_state::offscreen_pass_tag, - pass_state::idle_tag - >, - "Invalid transition: offscreen_pass -> idle" - ); - - // ... 实现代码 - } -}; -``` - ---- - -## 7. 与现有代码的集成策略 - -### 7.1 渐进式迁移方案 - -#### 第一阶段:并行实现 (2-3 天) - -``` -目标:创建新的类型安全接口,不修改现有代码 - -1. 创建新文件: - - src/render/pipeline/pass_state_tags.h - - src/render/pipeline/render_target_concept.h - - src/render/pipeline/frame_context_v2.h - - src/render/pipeline/nested_pass_context.h - -2. 在 frame_scheduler 中添加: - auto begin_frame_v2() -> std::optional>; - auto end_frame_v2(frame_context) -> bool; - -3. 新旧接口并存,通过编译开关选择 -``` - -#### 第二阶段:渲染器适配 (2-3 天) - -``` -目标:使渲染器符合 BatchRenderer concept - -1. 确保 geometry_renderer::render() 签名匹配 -2. 确保 image_renderer::render() 签名匹配 -3. 添加 concept 约束到渲染调用 -``` - -#### 第三阶段:遮罩渲染器重构 (3-4 天) - -``` -目标:分离遮罩渲染器的 RenderPass 控制 - -1. 重构 mask_renderer: - - 移除 begin_render/end_render 调用 - - 改为返回遮罩数据,由调用者控制 pass - -2. 创建 mask_pass_manager: - - 管理遮罩目标池 - - 提供类型安全的嵌套 pass 接口 -``` - -#### 第四阶段:后效重构 (2-3 天) - -``` -目标:使后效应用符合类型状态模型 - -1. 重构 post_effect_applicator: - - 接受 frame_context 而非裸 command buffer - - 返回新的 frame_context 状态 - -2. 更新后效接口: - auto apply(frame_context&&, const effect&) - -> frame_context; -``` - -#### 第五阶段:完全迁移 (1-2 天) - -``` -目标:移除旧接口 - -1. 删除 frame_scheduler::begin_frame() 旧版本 -2. 删除 render_pipeline 中的运行时状态跟踪 -3. 更新所有调用点使用新接口 -4. 移除编译开关 -``` - -### 7.2 迁移代码示例 - -#### 现有代码 (render_pipeline.cpp:397-514) - -```cpp -void render_pipeline::render_segments( - vk::CommandBuffer cmd, - std::span segments -) { - bool first_geometry = true; - bool render_pass_active = false; // 运行时状态跟踪 - offscreen_target* active_target = nullptr; - - for (const auto& segment : segments) { - // ... 复杂的状态管理逻辑 - } -} -``` - -#### 迁移后代码 - -```cpp -auto render_pipeline::render_segments_v2( - frame_context idle_ctx, - std::span segments -) -> frame_context { - - // 开始离屏渲染 - auto ctx = std::move(idle_ctx).begin_offscreen_pass(true); - - for (const auto& segment : segments) { - ctx = process_segment(std::move(ctx), segment); - } - - return std::move(ctx).end_offscreen_pass(); -} - -// 辅助函数:处理单个段 -auto render_pipeline::process_segment( - frame_context ctx, - const render_segment& segment -) -> frame_context { - - switch (segment.type) { - case segment_type::geometry: - for (const auto& batch : segment.batches) { - ctx.render_batch(*geometry_, batch); - } - return ctx; - - case segment_type::mask_begin: - return process_mask(std::move(ctx), segment); - - case segment_type::post_effect: - // 应用后效后返回 idle,然后重新开始 offscreen pass - auto idle = std::move(ctx).apply_post_effect( - segment.effect.value(), frame_time_ - ); - return std::move(idle).begin_offscreen_pass(false); - - default: - return ctx; - } -} -``` - -### 7.3 兼容性桥接 - -```cpp -// 为旧代码提供兼容层 -class legacy_frame_adapter { -public: - /// 从新接口适配到旧接口 - static auto to_legacy(frame_context&& ctx) - -> frame_context_legacy { - return frame_context_legacy{ - .command_buffer = ctx.resources().command_buffer, - .frame_index = ctx.resources().frame_index, - .image_index = ctx.resources().image_index, - .framebuffer = ctx.resources().swapchain_framebuffer - }; - } - - /// 从旧接口适配到新接口 - static auto from_legacy(const frame_context_legacy& legacy, - frame_resources additional) - -> frame_context { - additional.command_buffer = legacy.command_buffer; - additional.frame_index = legacy.frame_index; - additional.image_index = legacy.image_index; - additional.swapchain_framebuffer = legacy.framebuffer; - return frame_context(std::move(additional)); - } -}; -``` - ---- - -## 8. 性能证明 - -### 8.1 零开销抽象原理 - -#### 编译时消除 - -```cpp -// 状态标签是空结构体,不占用运行时空间 -struct idle_tag {}; // sizeof = 1 (C++ 最小对象大小) -struct offscreen_pass_tag {}; // sizeof = 1 - -// 但作为模板参数时,不产生运行时开销 -template -class frame_context { - // State 不作为成员存储,仅用于类型区分 - frame_resources res_; // 实际数据 -}; - -sizeof(frame_context) == sizeof(frame_context) - == sizeof(frame_resources) -``` - -#### 内联展开 - -```cpp -// 状态转换函数可被完全内联 -[[nodiscard]] -auto end_offscreen_pass(this frame_context&& self) - -> frame_context { - - // static_assert 在编译时完成,运行时无开销 - static_assert(is_valid_transition_v<...>); - - // 简单的成员操作,可被内联 - if (self.active_target_ && self.pass_active_) { - self.active_target_->end_render(self.res_.command_buffer, true); - self.pass_active_ = false; - } - - // 移动构造,无复制开销 - return frame_context(std::move(self.res_)); -} -``` - -### 8.2 汇编级别分析思路 - -编译以下代码并检查汇编输出: - -```cpp -// 测试代码 -void test_typestate(frame_context ctx) { - auto offscreen = std::move(ctx).begin_offscreen_pass(true); - // ... 渲染代码 - auto idle = std::move(offscreen).end_offscreen_pass(); -} - -void test_runtime(legacy_context ctx) { - ctx.begin_offscreen_pass(true); // 运行时状态检查 - // ... 渲染代码 - ctx.end_offscreen_pass(); // 运行时状态检查 -} -``` - -预期结果: -- `test_typestate`: 无条件分支用于状态检查,直接调用 Vulkan 函数 -- `test_runtime`: 包含条件分支检查 `render_pass_active` 等状态变量 - -### 8.3 与现有代码的性能对比 - -| 指标 | 现有实现 | Typestate 实现 | -|------|---------|---------------| -| 状态检查开销 | 运行时 if 检查 | 无 (编译时) | -| 虚函数调用 | 无 | 无 | -| 异常处理 | 可能使用 | 不使用 | -| 内存开销 | bool 状态变量 | 无额外开销 | -| 分支预测失败 | 可能 | 无分支 | - ---- - -## 9. 附录 - -### A. 完整文件结构 - -``` -src/render/pipeline/ -├── pass_state_tags.h # 状态标签和转换验证 -├── render_target_concept.h # RenderTarget/BatchRenderer concepts -├── frame_context_v2.h # 主帧上下文模板 -├── nested_pass_context.h # 嵌套 pass 上下文 -├── render_pipeline_v2.h # 新版渲染管线接口 -├── render_pipeline_v2.cpp # 新版实现 -└── legacy_adapter.h # 兼容性适配器 -``` - -### B. C++23 特性使用清单 - -| 特性 | 用途 | 文件 | -|-----|------|------| -| deducing this | 简化成员函数,避免 CRTP | frame_context_v2.h | -| std::expected | 无异常错误处理 | frame_context_v2.h | -| concepts | 约束模板参数 | render_target_concept.h | -| consteval | 编译时转换验证 | pass_state_tags.h | -| [[nodiscard]] | 强制处理返回值 | 所有状态转换函数 | -| std::move_only_function | 回调类型 (如需要) | - | - -### C. 编译器支持 - -| 编译器 | 最低版本 | 备注 | -|-------|---------|------| -| GCC | 13.0 | 完整 C++23 支持 | -| Clang | 17.0 | 完整 C++23 支持 | -| MSVC | 19.37 (VS 2022 17.7) | deducing this 支持 | - -### D. 参考资料 - -1. [C++23 Deducing This](https://en.cppreference.com/w/cpp/language/member_functions#Explicit_object_parameter) -2. [Typestate Pattern](https://cliffle.com/blog/rust-typestate/) -3. [Zero-Overhead Principle](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rp-waste) - ---- - -**文档结束** \ No newline at end of file diff --git a/docs/LAYOUT_UTILS_REFACTORING.md b/docs/LAYOUT_UTILS_REFACTORING.md deleted file mode 100644 index 66f7852..0000000 --- a/docs/LAYOUT_UTILS_REFACTORING.md +++ /dev/null @@ -1,524 +0,0 @@ -# 布局工具重构设计方案 - -## 1. 概述 - -本文档描述了将 `v_stack`、`h_stack` 和 `overlay` 容器中重复的布局属性操作封装成通用工具的重构方案。 - -### 1.1 目标 - -- 消除重复代码,提高代码可维护性 -- 保持与现有代码的完全兼容性 -- 使用模板元编程保持类型安全 -- 遵循项目现有的命名规范(snake_case) -- 最小化对现有容器代码的修改 - -### 1.2 已识别的重复模式 - -| 重复模式 | 出现位置 | 重复次数 | -|---------|---------|---------| -| `extract_margin()` | v_stack, h_stack, overlay | 3 | -| `extract_stretch()` | v_stack, h_stack | 2 | -| `has_auto_size()` | v_stack, h_stack | 2 | -| `should_stretch()` | v_stack, h_stack | 2 | -| `get_flex_factor()` | v_stack, h_stack | 2 | -| `get_event_children()` 实现 | v_stack, h_stack, overlay | 3 | -| 边距计算模式 | overlay, stack | 多处 | - -## 2. 设计方案 - -### 2.1 新文件结构 - -``` -src/widget/ -├── layout_utils.h # 新增:通用布局工具函数 -├── slot.h # 现有:保持不变 -├── stack.h # 修改:使用 layout_utils -├── overlay.h # 修改:使用 layout_utils -└── ... -``` - -### 2.2 layout_utils.h 接口设计 - -```cpp -#pragma once -#include "slot.h" -#include "event_target.h" -#include "dynamic_list.h" -#include -#include -#include - -namespace mirage { -namespace layout_utils { - -// ============================================================================ -// 参数提取工具函数 -// ============================================================================ - -/// @brief 从 slot 或普通 widget 中提取 margin 参数 -/// @tparam Child 子组件类型 -/// @param child 子组件引用 -/// @return 提取的 margin,如果不存在则返回 margin::zero() -template -constexpr margin extract_margin(const Child& child) { - if constexpr (is_slot_v) { - if (auto margin_p = child.template get()) { - return margin_p->value; - } - } - return margin::zero(); -} - -/// @brief 从 slot 中提取 stretch 参数 -/// @tparam Child 子组件类型 -/// @param child 子组件引用 -/// @return 提取的 stretch_param(可选) -template -constexpr std::optional extract_stretch(const Child& child) { - if constexpr (is_slot_v) { - return child.template get(); - } - return std::nullopt; -} - -/// @brief 从 slot 中提取 alignment 参数 -/// @tparam Child 子组件类型 -/// @param child 子组件引用 -/// @param default_align 默认对齐方式 -/// @return 提取的 alignment -template -constexpr alignment extract_alignment(const Child& child, - alignment default_align = alignment::TOP_LEFT) { - if constexpr (is_slot_v) { - if (auto align_p = child.template get()) { - return align_p->value; - } - } - return default_align; -} - -/// @brief 检查是否设置了 auto_size 参数 -/// @tparam Child 子组件类型 -/// @param child 子组件引用 -/// @return 如果设置了 auto_size 则返回 true -template -constexpr bool has_auto_size(const Child& child) { - if constexpr (is_slot_v) { - return child.template get().has_value(); - } - return false; -} - -/// @brief 判断子组件是否应该 stretch -/// @tparam Child 子组件类型 -/// @param child 子组件引用 -/// @return 如果应该 stretch 则返回 true -template -constexpr bool should_stretch(const Child& child) { - // 如果显式设置了 auto_size,不 stretch - if (has_auto_size(child)) return false; - // 如果设置了 stretch 参数,则 stretch - return extract_stretch(child).has_value(); -} - -/// @brief 获取 stretch 的 flex 因子 -/// @tparam Child 子组件类型 -/// @param child 子组件引用 -/// @return flex 因子,如果不存在则返回 0.0f -template -constexpr float get_flex_factor(const Child& child) { - if (auto sp = extract_stretch(child)) { - return sp->flex_factor; - } - return 0.0f; -} - -// ============================================================================ -// 边距计算工具函数 -// ============================================================================ - -/// @brief 计算减去 padding/margin 后的内部可用空间 -/// @param available_size 可用空间 -/// @param padding 边距 -/// @return 内部可用空间(保证非负) -inline vec2f_t calculate_inner_available(const vec2f_t& available_size, - const margin& padding) { - return vec2f_t( - std::max(0.0f, available_size.x() - padding.left - padding.right), - std::max(0.0f, available_size.y() - padding.top - padding.bottom) - ); -} - -/// @brief 计算水平方向的内部可用宽度 -/// @param available_width 可用宽度 -/// @param padding 边距 -/// @return 内部可用宽度(保证非负) -inline float calculate_inner_width(float available_width, const margin& padding) { - return std::max(0.0f, available_width - padding.left - padding.right); -} - -/// @brief 计算垂直方向的内部可用高度 -/// @param available_height 可用高度 -/// @param padding 边距 -/// @return 内部可用高度(保证非负) -inline float calculate_inner_height(float available_height, const margin& padding) { - return std::max(0.0f, available_height - padding.top - padding.bottom); -} - -/// @brief 计算 stretch 分配的尺寸 -/// @param flex_factor 当前组件的 flex 因子 -/// @param total_flex 总 flex 因子 -/// @param available_stretch 可用于 stretch 的空间 -/// @return 分配的尺寸 -inline float calculate_stretch_size(float flex_factor, float total_flex, - float available_stretch) { - if (total_flex <= 0.0f) return 0.0f; - return (flex_factor / total_flex) * available_stretch; -} - -// ============================================================================ -// 事件子组件收集工具 -// ============================================================================ - -/// @brief 从单个子组件中收集 event_target -/// @tparam Child 子组件类型 -/// @param child 子组件引用 -/// @param event_children 输出的 event_target 列表 -template -void collect_event_child(Child& child, std::vector& event_children) { - using ChildType = std::remove_cvref_t; - - // 情况 1: dynamic_list - if constexpr (std::is_same_v) { - event_children.push_back(const_cast(&child)); - } - // 情况 2: slot 类型 - else if constexpr (is_slot_v) { - using SlotChildType = slot_child_type_t; - if constexpr (std::is_base_of_v) { - event_children.push_back(const_cast(&child.child())); - } - } - // 情况 3: 继承自 event_target 的普通 widget - else if constexpr (std::is_base_of_v) { - event_children.push_back(const_cast(&child)); - } - // 情况 4: 其他 widget(不处理) -} - -/// @brief 从 tuple 中收集所有 event_target 子组件 -/// @tparam Tuple tuple 类型 -/// @param children 子组件 tuple -/// @return event_target 指针列表 -template -std::vector collect_event_children_from_tuple(Tuple& children) { - std::vector event_children; - - std::apply([&](auto&... child) { - (collect_event_child(child, event_children), ...); - }, children); - - return event_children; -} - -// ============================================================================ -// 子组件访问工具 -// ============================================================================ - -/// @brief 获取子组件的实际 widget 引用(解包 slot) -/// @tparam Child 子组件类型 -/// @param child 子组件引用 -/// @return 实际 widget 的引用 -template -decltype(auto) get_actual_child(Child& child) { - if constexpr (is_slot_v>) { - return child.child(); - } else { - return child; - } -} - -/// @brief 获取子组件的实际 widget 引用(const 版本) -template -decltype(auto) get_actual_child(const Child& child) { - if constexpr (is_slot_v>) { - return child.child(); - } else { - return child; - } -} - -} // namespace layout_utils -} // namespace mirage -``` - -## 3. 重构后的代码示例 - -### 3.1 v_stack 重构示例 - -**重构前(当前代码):** - -```cpp -// stack.h 中的 v_stack 类 -template -margin extract_margin(const Child& child) const { - if constexpr (is_slot_v) { - if (auto margin_p = child.template get()) { - return margin_p->value; - } - } - return margin::zero(); -} - -template -std::optional extract_stretch(const Child& child) const { - if constexpr (is_slot_v) { - return child.template get(); - } - return std::nullopt; -} - -// ... 更多重复方法 -``` - -**重构后:** - -```cpp -// stack.h 中的 v_stack 类 -#include "layout_utils.h" - -template -class v_stack : public event_target, public z_order_mixin> { -public: - // ... 构造函数等保持不变 - - std::vector get_event_children() const override { - // 使用工具函数简化实现 - return layout_utils::collect_event_children_from_tuple( - const_cast&>(children_) - ); - } - -private: - // 删除重复的私有方法,直接使用 layout_utils 命名空间中的函数 - - template - void calculate_flex_info(const Child& child, const vec2f_t& available_size, - float& fixed_height, float& total_flex) const { - const auto child_margin = layout_utils::extract_margin(child); - - if constexpr (std::is_same_v, dynamic_list>) { - for (const auto& sub_child : child.get_children()) { - const auto child_size = sub_child.measure(available_size); - fixed_height += child_size.y() + child_margin.top + child_margin.bottom; - } - } - else if constexpr (is_slot_v) { - if (layout_utils::should_stretch(child)) { - fixed_height += child_margin.top + child_margin.bottom; - total_flex += layout_utils::get_flex_factor(child); - } else { - const auto child_size = child.child().measure(available_size); - fixed_height += child_size.y() + child_margin.top + child_margin.bottom; - } - } - else { - const auto child_size = child.measure(available_size); - fixed_height += child_size.y() + child_margin.top + child_margin.bottom; - } - } - - // ... 其他方法类似重构 -}; -``` - -### 3.2 overlay 重构示例 - -**重构前:** - -```cpp -// overlay.h -template -static margin extract_margin(const Child& child) { - if constexpr (is_slot_v) { - if (auto margin_p = child.template get()) { - return margin_p->value; - } - } - return margin::zero(); -} - -// 边距计算 -const vec2f_t inner_available = vec2f_t( - std::max(0.0f, available_size.x() - padding.left - padding.right), - std::max(0.0f, available_size.y() - padding.top - padding.bottom) -); -``` - -**重构后:** - -```cpp -// overlay.h -#include "layout_utils.h" - -template -class overlay : public event_target, public z_order_mixin> { -public: - auto measure(const vec2f_t& available_size) const -> vec2f_t { - vec2f_t max_size = vec2f_t::Zero(); - - // 使用工具函数计算内部可用空间 - const vec2f_t inner_available = layout_utils::calculate_inner_available( - available_size, extra_padding_ - ); - - std::apply([&](const auto&... child) { - (process_child_measure(child, inner_available, max_size), ...); - }, children_); - - max_size.x() += extra_padding_.left + extra_padding_.right; - max_size.y() += extra_padding_.top + extra_padding_.bottom; - - return max_size; - } - - std::vector get_event_children() const override { - return layout_utils::collect_event_children_from_tuple( - const_cast&>(children_) - ); - } - -private: - template - alignment extract_alignment(const Child& child) const { - // 使用工具函数,传入默认对齐方式 - return layout_utils::extract_alignment(child, default_alignment_); - } - - template - void process_child_measure(const Child& child, const vec2f_t& available_size, - vec2f_t& max_size) const { - const auto align = extract_alignment(child); - const auto padding = layout_utils::extract_margin(child); - - // 使用工具函数计算内部可用空间 - const vec2f_t child_available = layout_utils::calculate_inner_available( - available_size, padding - ); - - // ... 其余逻辑保持不变 - } -}; -``` - -## 4. 重构步骤 - -### 4.1 阶段一:创建工具文件(低风险) - -1. 创建 `src/widget/layout_utils.h` -2. 实现所有工具函数 -3. 添加单元测试验证工具函数正确性 - -### 4.2 阶段二:逐步迁移(中等风险) - -1. **v_stack 迁移** - - 引入 `layout_utils.h` - - 替换私有方法调用为工具函数 - - 删除重复的私有方法 - - 运行测试验证 - -2. **h_stack 迁移** - - 同 v_stack 步骤 - -3. **overlay 迁移** - - 引入 `layout_utils.h` - - 替换 `extract_margin` 和边距计算 - - 替换 `get_event_children` 实现 - - 运行测试验证 - -### 4.3 阶段三:清理和优化(低风险) - -1. 移除所有容器中的重复代码 -2. 更新文档 -3. 代码审查 - -## 5. 可行性评估 - -### 5.1 技术可行性:高 - -- 所有重复代码模式清晰,易于抽象 -- 模板元编程技术成熟,项目已有使用先例 -- 不涉及运行时行为变更 - -### 5.2 兼容性:完全兼容 - -- 工具函数为纯函数,无副作用 -- 不改变任何公共 API -- 不改变任何运行时行为 - -### 5.3 风险评估 - -| 风险项 | 风险等级 | 缓解措施 | -|-------|---------|---------| -| 编译错误 | 低 | 逐步迁移,每步验证 | -| 模板实例化问题 | 低 | 充分的单元测试 | -| 性能回归 | 极低 | 内联函数,零开销抽象 | -| 行为变更 | 极低 | 工具函数逻辑与原代码完全一致 | - -## 6. 预期收益 - -### 6.1 代码量减少 - -| 文件 | 当前行数 | 预计减少 | 减少比例 | -|-----|---------|---------|---------| -| stack.h | ~564 | ~80 | ~14% | -| overlay.h | ~356 | ~40 | ~11% | -| **总计** | ~920 | ~120 | ~13% | - -### 6.2 维护性提升 - -- 单一职责:布局工具函数集中管理 -- 易于测试:工具函数可独立测试 -- 易于扩展:新容器可直接复用工具函数 - -### 6.3 一致性提升 - -- 所有容器使用相同的参数提取逻辑 -- 边距计算行为统一 -- 事件子组件收集逻辑统一 - -## 7. 架构图 - -```mermaid -graph TB - subgraph 重构前 - VS1[v_stack] --> |重复代码| M1[extract_margin] - VS1 --> |重复代码| S1[extract_stretch] - VS1 --> |重复代码| E1[get_event_children] - - HS1[h_stack] --> |重复代码| M2[extract_margin] - HS1 --> |重复代码| S2[extract_stretch] - HS1 --> |重复代码| E2[get_event_children] - - OV1[overlay] --> |重复代码| M3[extract_margin] - OV1 --> |重复代码| E3[get_event_children] - end - - subgraph 重构后 - LU[layout_utils.h] - LU --> EM[extract_margin] - LU --> ES[extract_stretch] - LU --> SS[should_stretch] - LU --> GF[get_flex_factor] - LU --> CIA[calculate_inner_available] - LU --> CEC[collect_event_children] - - VS2[v_stack] --> LU - HS2[h_stack] --> LU - OV2[overlay] --> LU - end -``` - -## 8. 结论 - -本重构方案具有高可行性和低风险,预计可减少约 13% 的重复代码,同时提升代码的可维护性和一致性。建议按照分阶段计划执行,每个阶段完成后进行充分测试。 \ No newline at end of file diff --git a/docs/MASK_WIDGET_DESIGN.md b/docs/MASK_WIDGET_DESIGN.md deleted file mode 100644 index 0329941..0000000 --- a/docs/MASK_WIDGET_DESIGN.md +++ /dev/null @@ -1,1054 +0,0 @@ - -# Mask 遮罩控件设计文档 - -## 1. 概述 - -本文档描述了 Mirage UI 框架中 `mask` 遮罩控件的设计方案。该控件能够对子控件应用形状遮罩,实现如圆形头像、圆角卡片等常见UI效果。 - -### 1.1 设计目标 - -- **声明式API**:与现有widget系统风格一致 -- **灵活的遮罩形状**:支持圆形、矩形、圆角矩形等多种形状 -- **高性能渲染**:利用GPU加速的遮罩效果 -- **可组合性**:支持与其他修饰器(padding、align等)组合使用 - -### 1.2 用户故事示例 - -```cpp -// 将图片裁剪成圆形 -mask { - imager{} - .texture_id(texture_id_) - .fit(image_fit::contain) - .set_texture_size(texture_size_) - | align(alignment::CENTER) - | padding(10.f), - circle_mask{} // 圆形遮罩 -} - -// 圆角矩形卡片 -mask { - v_stack { - text_widget{}.text("Title"), - imager{}.texture_id(img_id) - }, - rounded_rect_mask{}.corner_radius(16.f) -} -``` - -## 2. 架构设计 - -### 2.1 组件关系图 - -```mermaid -classDiagram - class widget { - <> - +measure(available_size) vec2f_t - +arrange(state) - +build_render_commands(builder) - } - - class mask_shape { - <> - +get_shape_type() mask_shape_type - +get_shape_params() mask_shape_params - } - - class circle_mask { - -radius_ratio_: float - +radius_ratio(float) self - } - - class rect_mask { - -inset_: margin - +inset(margin) self - } - - class rounded_rect_mask { - -corner_radius_: float - -corner_radius_ratio_: float - +corner_radius(float) self - +corner_radius_ratio(float) self - } - - class mask_widget~Child, Mask~ { - -child_: Child - -mask_: Mask - -layout_state_: layout_state - +measure(available_size) vec2f_t - +arrange(state) - +build_render_commands(builder) - } - - class mask_command { - +position: vec2f_t - +size: vec2f_t - +shape_type: mask_shape_type - +shape_params: mask_shape_params - +content_texture_id: uint32_t - +order: render_order - } - - mask_shape <|.. circle_mask - mask_shape <|.. rect_mask - mask_shape <|.. rounded_rect_mask - widget <|.. mask_widget - mask_widget --> mask_shape : uses - mask_widget ..> mask_command : generates -``` - -### 2.2 数据流 - -```mermaid -flowchart TB - subgraph Layout Pass - A[mask.measure] --> B[child.measure] - B --> C[返回子控件尺寸] - D[mask.arrange] --> E[child.arrange] - end - - subgraph Render Pass - F[mask.build_render_commands] --> G[开始遮罩层] - G --> H[child.build_render_commands] - H --> I[结束遮罩层并应用mask_shape] - I --> J[生成mask_command] - end - - subgraph GPU Rendering - K[渲染子控件到临时纹理] --> L[应用遮罩shader] - L --> M[输出到主渲染目标] - end -``` - -## 3. 详细设计 - -### 3.1 遮罩形状类型 - -```cpp -// src/widget/mask/mask_shape_types.h -#pragma once - -#include "types.h" -#include - -namespace mirage { - /// @brief 遮罩形状类型枚举 - enum class mask_shape_type : uint8_t { - circle, ///< 圆形遮罩 - ellipse, ///< 椭圆遮罩 - rect, ///< 矩形遮罩(可带内边距) - rounded_rect, ///< 圆角矩形遮罩 - custom ///< 自定义遮罩(SDF函数) - }; - - /// @brief 圆形遮罩参数 - struct circle_mask_params { - float radius_ratio = 0.5f; ///< 半径比例(相对于min(width, height)) - vec2f_t center_offset{0.f, 0.f}; ///< 中心偏移(相对于控件中心) - }; - - /// @brief 椭圆遮罩参数 - struct ellipse_mask_params { - vec2f_t radius_ratio{0.5f, 0.5f}; ///< 水平/垂直半径比例 - vec2f_t center_offset{0.f, 0.f}; - }; - - /// @brief 矩形遮罩参数 - struct rect_mask_params { - margin inset{0.f}; ///< 内边距 - }; - - /// @brief 圆角矩形遮罩参数 - struct rounded_rect_mask_params { - float corner_radius = 0.f; ///< 绝对圆角半径(像素) - float corner_radius_ratio = 0.f; ///< 相对圆角半径(0-0.5) - margin inset{0.f}; ///< 内边距 - }; - - /// @brief 自定义遮罩参数 - struct custom_mask_params { - uint32_t shader_id = 0; ///< 自定义SDF shader ID - shader_uniform_map uniforms; ///< shader参数 - }; - - /// @brief 遮罩形状参数联合类型 - using mask_shape_params = std::variant< - circle_mask_params, - ellipse_mask_params, - rect_mask_params, - rounded_rect_mask_params, - custom_mask_params - >; -} -``` - -### 3.2 遮罩形状基类 - -```cpp -// src/widget/mask/mask_shape.h -#pragma once - -#include "mask_shape_types.h" -#include - -namespace mirage { - /// @brief 遮罩形状concept - template - concept mask_shape = requires(const T& s) { - { s.get_shape_type() } -> std::same_as; - { s.get_shape_params() } -> std::same_as; - }; -} -``` - -### 3.3 具体遮罩形状实现 - -```cpp -// src/widget/mask/circle_mask.h -#pragma once - -#include "mask_shape_types.h" - -namespace mirage { - /// @brief 圆形遮罩 - class circle_mask { - public: - circle_mask() = default; - - /// @brief 设置半径比例(相对于min(width, height)的一半) - /// @param ratio 半径比例,1.0 = 内切圆,0.5 = 半径为控件尺寸的1/4 - auto&& radius_ratio(this auto&& self, float ratio) { - self.params_.radius_ratio = ratio; - return std::forward(self); - } - - /// @brief 设置中心偏移 - auto&& center_offset(this auto&& self, float x, float y) { - self.params_.center_offset = vec2f_t{x, y}; - return std::forward(self); - } - - [[nodiscard]] auto get_shape_type() const -> mask_shape_type { - return mask_shape_type::circle; - } - - [[nodiscard]] auto get_shape_params() const -> mask_shape_params { - return params_; - } - - private: - circle_mask_params params_; - }; -} - -// src/widget/mask/rounded_rect_mask.h -#pragma once - -#include "mask_shape_types.h" - -namespace mirage { - /// @brief 圆角矩形遮罩 - class rounded_rect_mask { - public: - rounded_rect_mask() = default; - - /// @brief 设置绝对圆角半径(像素) - auto&& corner_radius(this auto&& self, float radius) { - self.params_.corner_radius = radius; - self.params_.corner_radius_ratio = 0.f; - return std::forward(self); - } - - /// @brief 设置相对圆角半径(0-0.5,相对于min(width, height)) - auto&& corner_radius_ratio(this auto&& self, float ratio) { - self.params_.corner_radius_ratio = ratio; - self.params_.corner_radius = 0.f; - return std::forward(self); - } - - /// @brief 设置内边距 - auto&& inset(this auto&& self, float all) { - self.params_.inset = margin{all}; - return std::forward(self); - } - - auto&& inset(this auto&& self, margin m) { - self.params_.inset = m; - return std::forward(self); - } - - [[nodiscard]] auto get_shape_type() const -> mask_shape_type { - return mask_shape_type::rounded_rect; - } - - [[nodiscard]] auto get_shape_params() const -> mask_shape_params { - return params_; - } - - private: - rounded_rect_mask_params params_; - }; -} - -// src/widget/mask/rect_mask.h -#pragma once - -#include "mask_shape_types.h" - -namespace mirage { - /// @brief 矩形遮罩 - class rect_mask { - public: - rect_mask() = default; - - /// @brief 设置内边距 - auto&& inset(this auto&& self, float all) { - self.params_.inset = margin{all}; - return std::forward(self); - } - - auto&& inset(this auto&& self, margin m) { - self.params_.inset = m; - return std::forward(self); - } - - [[nodiscard]] auto get_shape_type() const -> mask_shape_type { - return mask_shape_type::rect; - } - - [[nodiscard]] auto get_shape_params() const -> mask_shape_params { - return params_; - } - - private: - rect_mask_params params_; - }; -} -``` - -### 3.4 Mask Widget 实现 - -```cpp -// src/widget/mask/mask_widget.h -#pragma once - -#include "widget.h" -#include "layout_state.h" -#include "mask_shape.h" -#include "event_target.h" -#include - -namespace mirage { - /// @brief 遮罩容器控件 - /// - /// 将子控件渲染内容应用指定的遮罩形状。 - /// 遮罩外的区域将被裁剪(透明)。 - /// - /// @tparam Child 子控件类型(满足widget concept) - /// @tparam Mask 遮罩形状类型(满足mask_shape concept) - template - class mask_widget : public event_target, public z_order_mixin> { - public: - /// @brief 构造函数 - /// @param child 子控件 - /// @param mask 遮罩形状 - mask_widget(Child child, Mask mask) - : child_(std::move(child)) - , mask_(std::move(mask)) { - } - - // ======================================================================== - // Widget concept 实现 - // ======================================================================== - - /// @brief 测量阶段 - 代理到子控件 - [[nodiscard]] auto measure(const vec2f_t& available_size) const -> vec2f_t { - return child_.measure(available_size); - } - - /// @brief 布局阶段 - 保存布局状态并代理到子控件 - void arrange(const layout_state& state) { - layout_state_ = state; - child_.arrange(state); - } - - /// @brief 构建渲染命令 - void build_render_commands(render_command_builder& builder) const { - // 应用 z_order - this->apply_z_order_to_builder(builder); - - // 生成遮罩渲染命令 - // 方案1: 使用push_mask/pop_mask机制(需要扩展render_command_builder) - // 方案2: 使用专门的mask_command(推荐) - - // 推入遮罩上下文 - builder.push_mask( - layout_state_.global_pos(), - layout_state_.size(), - mask_.get_shape_type(), - mask_.get_shape_params() - ); - - // 渲染子控件 - child_.build_render_commands(builder); - - // 弹出遮罩上下文 - builder.pop_mask(); - } - - // ======================================================================== - // 访问器 - // ======================================================================== - - [[nodiscard]] auto& child() { return child_; } - [[nodiscard]] const auto& child() const { return child_; } - - [[nodiscard]] auto& mask() { return mask_; } - [[nodiscard]] const auto& mask() const { return mask_; } - - // ======================================================================== - // event_target 接口实现 - // ======================================================================== - - bool contains_point(double global_x, double global_y) const override { - if (!layout_state_.contains_global_point( - vec2f_t(static_cast(global_x), static_cast(global_y)))) { - return false; - } - - // TODO: 可选实现遮罩形状内的点击检测 - // 当前简化为矩形检测 - return true; - } - - std::pair global_to_local(double global_x, double global_y) const override { - auto local = layout_state_.to_local( - vec2f_t(static_cast(global_x), static_cast(global_y))); - return {local.x(), local.y()}; - } - - std::vector get_event_children() const override { - std::vector children; - if constexpr (std::is_base_of_v) { - children.push_back(const_cast(&child_)); - } - return children; - } - - private: - Child child_; - Mask mask_; - mutable layout_state layout_state_; - }; - - // ======================================================================== - // 便捷工厂函数 - // ======================================================================== - - /// @brief 创建遮罩控件 - template - auto mask(Child&& child, Mask&& mask_shape) { - return mask_widget, std::decay_t>( - std::forward(child), - std::forward(mask_shape) - ); - } - - /// @brief 创建圆形遮罩控件 - template - auto circle_masked(Child&& child) { - return mask(std::forward(child), circle_mask{}); - } - - /// @brief 创建圆角矩形遮罩控件 - template - auto rounded_masked(Child&& child, float corner_radius) { - return mask(std::forward(child), - rounded_rect_mask{}.corner_radius(corner_radius)); - } -} -``` - -### 3.5 渲染命令扩展 - -```cpp -// 在 src/common/render_command.h 中添加 - -namespace mirage { - // ============================================================================ - // 遮罩渲染命令 - // ============================================================================ - - /// @brief 遮罩开始命令 - struct mask_begin_command { - vec2f_t position; ///< 遮罩区域位置 - vec2f_t size; ///< 遮罩区域大小 - mask_shape_type shape_type; ///< 遮罩形状类型 - mask_shape_params shape_params; ///< 遮罩形状参数 - render_order order; ///< 渲染顺序 - uint32_t mask_id; ///< 遮罩ID(用于配对begin/end) - }; - - /// @brief 遮罩结束命令 - struct mask_end_command { - uint32_t mask_id; ///< 遮罩ID(与begin配对) - render_order order; ///< 渲染顺序 - }; - - // 更新 render_command variant - using render_command = std::variant< - rectangle_command, - text_command, - image_command, - debug_rect_command, - post_effect_command, - mask_begin_command, // 新增 - mask_end_command // 新增 - >; -} - -// 在 render_command_builder 类中添加遮罩支持 -class render_command_builder { -public: - // ... 现有方法 ... - - /// @brief 推入遮罩上下文 - void push_mask( - const vec2f_t& position, - const vec2f_t& size, - mask_shape_type shape_type, - const mask_shape_params& shape_params - ) { - uint32_t mask_id = next_mask_id_++; - mask_id_stack_.push_back(mask_id); - - mask_begin_command cmd; - cmd.position = position; - cmd.size = size; - cmd.shape_type = shape_type; - cmd.shape_params = shape_params; - cmd.order = render_order::at(current_z_order_, get_current_clip()); - cmd.mask_id = mask_id; - - commands_.emplace_back(std::move(cmd)); - } - - /// @brief 弹出遮罩上下文 - void pop_mask() { - if (mask_id_stack_.empty()) { - return; - } - - uint32_t mask_id = mask_id_stack_.back(); - mask_id_stack_.pop_back(); - - mask_end_command cmd; - cmd.mask_id = mask_id; - cmd.order = render_order::at(current_z_order_, get_current_clip()); - - commands_.emplace_back(std::move(cmd)); - } - -private: - std::vector mask_id_stack_; - uint32_t next_mask_id_ = 0; -}; -``` - -## 4. 渲染实现方案 - -### 4.1 渲染流程 - -遮罩渲染采用**离屏渲染 + SDF着色器**的方案: - -```mermaid -sequenceDiagram - participant CP as CommandProcessor - participant MR as MaskRenderer - participant OT as OffscreenTarget - participant TT as TempTarget - participant Shader as MaskShader - - CP->>MR: 检测到mask_begin_command - MR->>TT: 创建/获取临时渲染目标 - MR->>TT: 开始渲染到临时目标 - - loop 遮罩内的命令 - CP->>TT: 渲染几何/图片命令 - end - - CP->>MR: 检测到mask_end_command - MR->>Shader: 应用遮罩shader - Note over Shader: 采样临时目标纹理
计算SDF遮罩alpha
输出遮罩后的像素 - MR->>OT: 将遮罩结果渲染到主目标 - MR->>TT: 释放临时目标 -``` - -### 4.2 SDF遮罩着色器 - -```glsl -// src/render/shaders/widget/mask.frag.glsl -#version 450 - -layout(location = 0) in vec2 fragUV; -layout(location = 0) out vec4 outColor; - -layout(binding = 0) uniform sampler2D contentTexture; - -layout(push_constant) uniform PushConstants { - vec2 position; // 遮罩位置 - vec2 size; // 遮罩大小 - uint shapeType; // 遮罩形状类型 - float param1; // 形状参数1(圆角半径/半径比例) - float param2; // 形状参数2 - float param3; // 形状参数3 - float param4; // 形状参数4 -} mask; - -// SDF函数:圆形 -float sdCircle(vec2 p, float r) { - return length(p) - r; -} - -// SDF函数:圆角矩形 -float sdRoundedRect(vec2 p, vec2 b, float r) { - vec2 q = abs(p) - b + r; - return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r; -} - -// SDF函数:矩形 -float sdRect(vec2 p, vec2 b) { - vec2 d = abs(p) - b; - return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); -} - -void main() { - // 采样内容纹理 - vec4 content = texture(contentTexture, fragUV); - - // 计算当前像素在遮罩空间中的位置(-0.5 到 0.5) - vec2 uv = fragUV - 0.5; - vec2 aspectRatio = mask.size / min(mask.size.x, mask.size.y); - vec2 p = uv * aspectRatio; - - float dist = 0.0; - float smoothing = 1.0 / min(mask.size.x, mask.size.y); // 抗锯齿 - - // 根据形状类型计算SDF - if (mask.shapeType == 0) { // circle - float radius = mask.param1 * 0.5; - dist = sdCircle(p + vec2(mask.param2, mask.param3), radius); - } - else if (mask.shapeType == 1) { // ellipse - vec2 radius = vec2(mask.param1, mask.param2) * 0.5; - dist = length(p / radius) - 1.0; - } - else if (mask.shapeType == 2) { // rect - vec2 inset = vec2(mask.param1, mask.param2) / mask.size; - dist = sdRect(p, 0.5 * aspectRatio - inset); - } - else if (mask.shapeType == 3) { // rounded_rect - float cornerRadius = mask.param1 / min(mask.size.x, mask.size.y); - vec2 inset = vec2(mask.param2, mask.param3) / mask.size; - dist = sdRoundedRect(p, 0.5 * aspectRatio - inset, cornerRadius); - } - - // 计算遮罩alpha(使用smoothstep实现抗锯齿) - float maskAlpha = 1.0 - smoothstep(-smoothing, smoothing, dist); - - // 应用遮罩 - outColor = vec4(content.rgb, content.a * maskAlpha); -} -``` - -### 4.3 MaskRenderer 组件 - -```cpp -// src/render/pipeline/mask_renderer.h -#pragma once - -#include "offscreen_target.h" -#include "render_command.h" -#include -#include - -namespace mirage { - /// @brief 遮罩渲染器 - /// - /// 负责处理mask_begin/mask_end命令对, - /// 使用临时渲染目标和SDF着色器实现遮罩效果。 - class mask_renderer { - public: - struct config { - uint32_t frames_in_flight = 2; - uint32_t max_nested_masks = 4; ///< 最大嵌套遮罩层数 - }; - - mask_renderer( - logical_device& device, - resource_manager& res_mgr, - const config& cfg = {} - ); - - ~mask_renderer(); - - /// @brief 初始化 - void initialize(uint32_t width, uint32_t height); - - /// @brief 调整大小 - void resize(uint32_t width, uint32_t height); - - /// @brief 开始遮罩 - /// @return 临时渲染目标,用于渲染遮罩内容 - offscreen_target& begin_mask( - vk::CommandBuffer cmd, - const mask_begin_command& mask_cmd - ); - - /// @brief 结束遮罩,应用遮罩效果并渲染到主目标 - void end_mask( - vk::CommandBuffer cmd, - offscreen_target& main_target, - const mask_end_command& mask_cmd - ); - - /// @brief 清理资源 - void cleanup(); - - private: - logical_device& device_; - resource_manager& res_mgr_; - config config_; - - // 临时渲染目标池 - std::vector> temp_targets_; - std::stack available_targets_; // 可用目标索引 - std::stack> active_masks_; // 活动遮罩 - - // 遮罩着色器管线 - vk::Pipeline mask_pipeline_; - vk::PipelineLayout mask_layout_; - vk::DescriptorSetLayout mask_desc_layout_; - vk::DescriptorPool mask_desc_pool_; - vk::Sampler mask_sampler_; - std::vector mask_descriptor_sets_; - - void create_pipeline(); - void create_descriptors(); - size_t acquire_temp_target(); - void release_temp_target(size_t index); - }; -} -``` - -## 5. 渲染管线集成 - -### 5.1 command_processor 扩展 - -需要修改 `command_processor` 以识别遮罩命令并生成对应的渲染段: - -```cpp -// 在 command_processor.cpp 中添加遮罩段类型 - -enum class segment_type { - geometry, // 几何渲染段 - post_effect, // 后效段 - mask_begin, // 遮罩开始段 (新增) - mask_end // 遮罩结束段 (新增) -}; - -struct render_segment { - segment_type type; - std::vector batches; // geometry 段使用 - std::optional effect; // post_effect 段使用 - std::optional mask_begin; // mask_begin 段使用 (新增) - std::optional mask_end; // mask_end 段使用 (新增) -}; -``` - -### 5.2 render_pipeline 集成 - -在 `render_pipeline::render_segments` 中添加遮罩段处理: - -```cpp -void render_pipeline::render_segments( - vk::CommandBuffer cmd, - std::span segments -) { - bool first_geometry = true; - - for (const auto& segment : segments) { - switch (segment.type) { - case segment_type::geometry: - // ... 现有几何渲染逻辑 ... - break; - - case segment_type::post_effect: - // ... 现有后效逻辑 ... - break; - - case segment_type::mask_begin: - // 开始遮罩:切换到临时渲染目标 - if (segment.mask_begin.has_value()) { - auto& temp_target = mask_renderer_->begin_mask( - cmd, - segment.mask_begin.value() - ); - // 后续几何命令将渲染到 temp_target - current_render_target_ = &temp_target; - } - break; - - case segment_type::mask_end: - // 结束遮罩:应用遮罩效果并渲染回主目标 - if (segment.mask_end.has_value()) { - mask_renderer_->end_mask( - cmd, - *offscreen_, - segment.mask_end.value() - ); - current_render_target_ = offscreen_.get(); - } - break; - } - } -} -``` - -## 6. API 使用示例 - -### 6.1 基本用法 - -```cpp -#include "widget/mask/mask_widget.h" -#include "widget/mask/circle_mask.h" -#include "widget/mask/rounded_rect_mask.h" -#include "widget/imager.h" -#include "widget/layout/modifiers/modifiers.h" - -using namespace mirage; - -// 示例1: 圆形头像 -auto avatar = mask( - imager{} - .texture_id(avatar_texture_id) - .fit(image_fit::cover) - .set_texture_size(256, 256), - circle_mask{} -); - -// 示例2: 带圆角的卡片 -auto card = mask( - v_stack( - text_widget{}.text("Card Title"), - imager{}.texture_id(card_image_id) - ), - rounded_rect_mask{}.corner_radius(12.f) -); - -// 示例3: 组合修饰器 -auto padded_avatar = mask( - imager{} - .texture_id(avatar_texture_id) - .fit(image_fit::cover) - | padding(8.f) - | align(alignment::CENTER), - circle_mask{}.radius_ratio(0.9f) // 稍微缩小以留出边距 -); -``` - -### 6.2 便捷函数 - -```cpp -// 使用便捷函数创建圆形遮罩 -auto circle_avatar = circle_masked( - imager{}.texture_id(avatar_id).fit(image_fit::cover) -); - -// 使用便捷函数创建圆角遮罩 -auto rounded_card = rounded_masked( - my_card_content, - 16.f // corner_radius -); -``` - -### 6.3 嵌套遮罩 - -```cpp -// 嵌套遮罩示例(外层圆角矩形,内层圆形) -auto nested_mask = mask( - mask( - imager{}.texture_id(texture_id), - circle_mask{} - ) | padding(16.f), - rounded_rect_mask{}.corner_radius(8.f) -); -``` - -### 6.4 与布局容器组合 - -```cpp -// 在 overlay 中使用遮罩 -auto profile_card = overlay( - // 背景图 - mask( - imager{}.texture_id(bg_id).fit(image_fit::cover), - rounded_rect_mask{}.corner_radius(16.f) - ), - // 头像(居中) - mask( - imager{}.texture_id(avatar_id).fit(image_fit::cover), - circle_mask{} - ) | align(alignment::CENTER) | padding(20.f) -); -``` - -## 7. 实现步骤建议 - -### 阶段 1: 基础结构 (2-3天) - -1. **创建文件结构** - - 创建 `src/widget/mask/` 目录 - - 实现 `mask_shape_types.h` - 形状类型定义 - - 实现 `mask_shape.h` - 形状 concept - - 实现 `circle_mask.h`, `rect_mask.h`, `rounded_rect_mask.h` - -2. **实现 mask_widget** - - 创建 `mask_widget.h` - - 实现 widget concept 的三个方法 - - 实现 event_target 接口 - -3. **扩展 render_command** - - 在 `render_command.h` 中添加 `mask_begin_command` 和 `mask_end_command` - - 在 `render_command_builder` 中添加 `push_mask()` 和 `pop_mask()` - -### 阶段 2: 渲染器实现 (3-4天) - -4. **创建遮罩着色器** - - 创建 `src/render/shaders/widget/mask.vert.glsl` - - 创建 `src/render/shaders/widget/mask.frag.glsl` - - 实现 SDF 遮罩算法 - -5. **实现 MaskRenderer** - - 创建 `src/render/pipeline/mask_renderer.h` 和 `.cpp` - - 实现临时渲染目标池管理 - - 实现遮罩管线创建 - - 实现 `begin_mask()` 和 `end_mask()` 方法 - -### 阶段 3: 管线集成 (2-3天) - -6. **扩展 command_processor** - - 添加 `mask_begin` 和 `mask_end` 段类型 - - 修改命令处理逻辑以识别遮罩命令 - -7. **集成到 render_pipeline** - - 添加 `mask_renderer_` 成员 - - 在 `initialize()` 中初始化遮罩渲染器 - - 在 `render_segments()` 中处理遮罩段 - - 在 `on_resize()` 中处理遮罩渲染器的尺寸调整 - -### 阶段 4: 测试和优化 (2天) - -8. **单元测试** - - 测试遮罩形状参数 - - 测试 mask_widget 的 measure/arrange - - 测试渲染命令生成 - -9. **集成测试** - - 创建测试示例程序 - - 验证各种遮罩形状 - - 测试嵌套遮罩 - - 性能测试 - -10. **优化** - - 优化临时渲染目标复用 - - 考虑遮罩缓存机制 - - 优化着色器性能 - -## 8. 未来扩展 - -### 8.1 自定义 SDF 遮罩 - -支持用户提供自定义 SDF 函数: - -```cpp -auto custom = mask( - content, - custom_mask{} - .shader_id(my_sdf_shader) - .set_uniform("param1", 0.5f) -); -``` - -### 8.2 动画遮罩 - -支持遮罩形状的动画: - -```cpp -auto animated_mask = mask( - content, - circle_mask{} - .radius_ratio(animation.value()) // 动画值 -); -``` - -### 8.3 遮罩过渡效果 - -支持遮罩形状之间的平滑过渡: - -```cpp -auto transition = mask_transition( - content, - circle_mask{}, // from - rect_mask{}, // to - transition_progress // 0.0 - 1.0 -); -``` - -### 8.4 软边缘遮罩 - -支持可配置的边缘羽化: - -```cpp -auto soft_mask = mask( - content, - circle_mask{} - .feather(4.f) // 4像素羽化边缘 -); -``` - -## 9. 性能考虑 - -### 9.1 临时渲染目标池 - -- 预分配固定数量的临时渲染目标(建议 4 个) -- 使用栈式分配/释放策略 -- 支持嵌套遮罩的深度限制 - -### 9.2 遮罩合并优化 - -- 相邻的相同类型遮罩可以合并处理 -- 静态遮罩可以缓存结果 -- 考虑使用 stencil buffer 作为备选方案 - -### 9.3 内存管理 - -- 临时目标尺寸应与主渲染目标一致 -- 窗口 resize 时需要重建所有临时目标 -- 考虑延迟创建临时目标以节省内存 - -## 10. 总结 - -本设计文档描述了 Mirage UI 框架中 mask 遮罩控件的完整架构设计,包括: - -- **Widget 层**:`mask_widget` 模板类和各种遮罩形状类 -- **命令层**:`mask_begin_command` 和 `mask_end_command` 渲染命令 -- **渲染层**:`mask_renderer` 组件和 SDF 遮罩着色器 -- **集成层**:与现有渲染管线的集成方案 - -设计遵循了 Mirage 框架的现有模式: -- 使用 C++ concepts 定义接口 -- 支持 C++23 deducing this 的链式调用 -- 与现有修饰器系统兼容 -- 遵循命令式渲染架构 \ No newline at end of file diff --git a/example/new_pipeline/new_pipeline_example.cpp b/example/new_pipeline/new_pipeline_example.cpp index a7293b3..ff5cfa2 100644 --- a/example/new_pipeline/new_pipeline_example.cpp +++ b/example/new_pipeline/new_pipeline_example.cpp @@ -432,22 +432,22 @@ private: }, // 使用 | 管道操作符 mask_widget{ - rounded_rect_mask{}.corner_radius(10.f), + rounded_rect_mask{}.corner_radius(30.f), overlay{ // 使用管道语法设置图片居中对齐和边距 imager{} .texture_id(texture_id_) .fit(image_fit::contain) .set_texture_size(texture_size_) - | align(alignment::CENTER) - | padding(10.f), + | align(alignment::CENTER), + // | padding(10.f), // 普通后效控件 post_effect_widget{ blur_effect{20.f}, fill_box{} }, - } - } | stretch() + } | stretch() + } }; layout_state state; diff --git a/src/render/pipeline/mask_renderer.cpp b/src/render/pipeline/mask_renderer.cpp index a50a0ee..3a60542 100644 --- a/src/render/pipeline/mask_renderer.cpp +++ b/src/render/pipeline/mask_renderer.cpp @@ -494,6 +494,13 @@ namespace mirage { float right = bounds.max().x(); float bottom = bounds.max().y(); + // 计算 UV 坐标(基于视口大小) + // UV 坐标需要映射到离屏目标中子控件的实际位置 + float uv_left = left / viewport_size_.x(); + float uv_top = top / viewport_size_.y(); + float uv_right = right / viewport_size_.x(); + float uv_bottom = bottom / viewport_size_.y(); + // 使用实际边界框尺寸,而不是遮罩参数中的尺寸 // 这样着色器才能正确计算非正方形区域中的圆形遮罩 float width = bounds.sizes().x(); @@ -514,8 +521,8 @@ namespace mirage { vertices[0].color[1] = 1.0f; vertices[0].color[2] = 1.0f; vertices[0].color[3] = 1.0f; - vertices[0].uv[0] = 0.0f; - vertices[0].uv[1] = 0.0f; + vertices[0].uv[0] = uv_left; + vertices[0].uv[1] = uv_top; vertices[0].data_a[0] = mask_type_f; vertices[0].data_a[1] = params.softness; vertices[0].data_a[2] = width; @@ -532,8 +539,8 @@ namespace mirage { vertices[1].color[1] = 1.0f; vertices[1].color[2] = 1.0f; vertices[1].color[3] = 1.0f; - vertices[1].uv[0] = 1.0f; - vertices[1].uv[1] = 0.0f; + vertices[1].uv[0] = uv_right; + vertices[1].uv[1] = uv_top; vertices[1].data_a[0] = mask_type_f; vertices[1].data_a[1] = params.softness; vertices[1].data_a[2] = width; @@ -550,8 +557,8 @@ namespace mirage { vertices[2].color[1] = 1.0f; vertices[2].color[2] = 1.0f; vertices[2].color[3] = 1.0f; - vertices[2].uv[0] = 1.0f; - vertices[2].uv[1] = 1.0f; + vertices[2].uv[0] = uv_right; + vertices[2].uv[1] = uv_bottom; vertices[2].data_a[0] = mask_type_f; vertices[2].data_a[1] = params.softness; vertices[2].data_a[2] = width; @@ -568,8 +575,8 @@ namespace mirage { vertices[3].color[1] = 1.0f; vertices[3].color[2] = 1.0f; vertices[3].color[3] = 1.0f; - vertices[3].uv[0] = 0.0f; - vertices[3].uv[1] = 1.0f; + vertices[3].uv[0] = uv_left; + vertices[3].uv[1] = uv_bottom; vertices[3].data_a[0] = mask_type_f; vertices[3].data_a[1] = params.softness; vertices[3].data_a[2] = width; diff --git a/src/widget/mask/circle_mask.h b/src/widget/mask/circle_mask.h index 3bf2432..bd7a2d6 100644 --- a/src/widget/mask/circle_mask.h +++ b/src/widget/mask/circle_mask.h @@ -44,7 +44,7 @@ namespace mirage { /// @brief 根据边界框计算遮罩参数 /// @param bounds 边界框 /// @return 遮罩参数 - [[nodiscard]] auto get_mask_params(aabb2d_t bounds) const -> mask_params { + [[nodiscard]] auto get_mask_params(const aabb2d_t& bounds) const -> mask_params { const vec2f_t center = bounds.center(); const vec2f_t size = bounds.sizes(); diff --git a/src/widget/mask/mask_widget.h b/src/widget/mask/mask_widget.h index 8a7734c..3479cac 100644 --- a/src/widget/mask/mask_widget.h +++ b/src/widget/mask/mask_widget.h @@ -136,8 +136,8 @@ namespace mirage { template auto mask(Child&& child, Mask&& m) { return mask_widget, std::decay_t>( - std::forward(child), - std::forward(m) + std::forward(m), + std::forward(child) ); } } // namespace mirage diff --git a/src/widget/mask/rect_mask.h b/src/widget/mask/rect_mask.h index a01ddc5..4a3e568 100644 --- a/src/widget/mask/rect_mask.h +++ b/src/widget/mask/rect_mask.h @@ -43,7 +43,7 @@ namespace mirage { /// @brief 根据边界框计算遮罩参数 /// @param bounds 边界框 /// @return 遮罩参数 - [[nodiscard]] auto get_mask_params(aabb2d_t bounds) const -> mask_params { + [[nodiscard]] auto get_mask_params(const aabb2d_t& bounds) const -> mask_params { const vec2f_t center = bounds.center(); const vec2f_t size = bounds.sizes(); diff --git a/src/widget/mask/rounded_rect_mask.h b/src/widget/mask/rounded_rect_mask.h index 574e22a..b952e36 100644 --- a/src/widget/mask/rounded_rect_mask.h +++ b/src/widget/mask/rounded_rect_mask.h @@ -30,19 +30,8 @@ namespace mirage { /// @brief 设置统一圆角半径(四个角相同) /// @param r 圆角半径 /// @return 返回自身引用,支持链式调用(包括临时对象) - auto&& corner_radius(this auto&& self, float r) { - self.corner_radii_ = vec4f_t(r, r, r, r); - return std::forward(self); - } - - /// @brief 设置四个角分别的圆角半径 - /// @param tl 左上角半径 - /// @param tr 右上角半径 - /// @param br 右下角半径 - /// @param bl 左下角半径 - /// @return 返回自身引用,支持链式调用(包括临时对象) - auto&& corner_radius(this auto&& self, float tl, float tr, float br, float bl) { - self.corner_radii_ = vec4f_t(tl, tr, br, bl); + auto&& corner_radius(this auto&& self, const rect_corner_radius r) { + self.corner_radii_ = vec4f_t(r.top_left, r.top_right, r.bottom_right, r.bottom_left); return std::forward(self); } @@ -75,7 +64,7 @@ namespace mirage { /// @brief 根据边界框计算遮罩参数 /// @param bounds 边界框 /// @return 遮罩参数 - [[nodiscard]] auto get_mask_params(aabb2d_t bounds) const -> mask_params { + [[nodiscard]] auto get_mask_params(const aabb2d_t& bounds) const -> mask_params { const vec2f_t center = bounds.center(); const vec2f_t size = bounds.sizes();