Refactor widget framework for thread synchronization and improve text rendering quality

- Removed the WIDGET_THREAD_SYNC_REFACTORING_PLAN.md document as part of the restructuring process.
- Enhanced the atlas GPU resource to enable anisotropic filtering for better text edge quality.
- Updated MTSDF text shaders to dynamically pass pixel range for improved anti-aliasing based on screen density.
- Modified glyph cache to calculate pixel range based on glyph size, ensuring consistency in rendering.
- Adjusted text shaping logic to utilize actual glyph size for scaling, improving text rendering accuracy.
- Refined IME handling in Windows to disable system IME UI while allowing candidate window display.
This commit is contained in:
2025-12-14 20:22:37 +08:00
parent d0c1afd755
commit 688f03f43a
22 changed files with 150 additions and 12882 deletions

View File

@@ -1,868 +0,0 @@
# 异步MTSDF纹理生成架构设计
## 目录
1. [概述](#概述)
2. [整体架构](#整体架构)
3. [async_mtsdf_generator 设计](#async_mtsdf_generator-设计)
4. [glyph_cache 改进](#glyph_cache-改进)
5. [纹理上传批处理](#纹理上传批处理)
6. [线程安全策略](#线程安全策略)
7. [接口定义](#接口定义)
---
## 概述
### 背景
当前MTSDF字形生成是同步阻塞的在主线程或渲染线程中执行会导致帧率下降。本设计旨在将MTSDF生成移至后台线程池实现异步非阻塞的字形生成。
### 设计目标
- **性能**: 将CPU密集的MTSDF生成移至后台线程不阻塞渲染
- **响应性**: 使用占位符机制,字形请求立即返回
- **线程安全**: 正确处理FreeType的线程限制和图集的并发访问
- **兼容性**: 复用现有的 `future_chain<T>``thread_dispatcher` 框架
### 核心挑战
| 挑战 | 解决方案 |
|------|----------|
| `FT_Face` 非线程安全 | 每个Worker线程独立的 `font_manager` 实例 |
| 图集并发修改 | 互斥锁保护 + 批量更新策略 |
| GPU纹理上传 | 渲染线程专属上传 + 脏区域追踪 |
---
## 整体架构
### 组件关系图
```mermaid
graph TB
subgraph 调用层
TS[text_shaper]
TR[text_renderer]
end
subgraph 缓存层
GC[glyph_cache_async]
end
subgraph 生成层
AMG[async_mtsdf_generator]
subgraph Worker Pool
W1[Worker 1<br/>font_manager + queue]
W2[Worker 2<br/>font_manager + queue]
WN[Worker N<br/>font_manager + queue]
end
end
subgraph 存储层
FA[font_atlas]
TM[texture_manager]
end
TS --> GC
TR --> GC
GC -->|request_glyph| AMG
AMG --> W1
AMG --> W2
AMG --> WN
W1 -->|bitmap| GC
W2 -->|bitmap| GC
WN -->|bitmap| GC
GC -->|add_glyph| FA
FA -->|upload| TM
```
### 数据流图
```mermaid
sequenceDiagram
participant Caller as text_shaper
participant Cache as glyph_cache_async
participant Gen as async_mtsdf_generator
participant Worker as Worker Thread
participant Atlas as font_atlas
participant Render as Render Thread
Caller->>Cache: get_or_create_glyph_async
alt 缓存命中
Cache-->>Caller: glyph_rect 指针
else 缓存未命中
Cache->>Cache: 创建 pending 占位符
Cache-->>Caller: nullptr 或占位符
Cache->>Gen: submit_task
Gen->>Worker: 调度到空闲Worker
Worker->>Worker: load_glyph + generate_mtsdf
Worker-->>Gen: glyph_bitmap
Gen->>Cache: on_complete 回调
Cache->>Atlas: add_glyph with mutex
Atlas->>Atlas: 标记 dirty region
end
Note over Render: 帧开始
Render->>Atlas: flush_pending_uploads
Atlas->>Render: 批量上传脏区域
```
### 新增与修改组件
| 组件 | 类型 | 描述 |
|------|------|------|
| `async_mtsdf_generator` | 新增 | 管理Worker线程池调度MTSDF生成任务 |
| `mtsdf_worker` | 新增 | Worker线程上下文持有独立的FreeType资源 |
| `glyph_cache` | 修改 | 添加异步接口和状态管理 |
| `font_atlas` | 修改 | 添加线程安全和脏区域追踪 |
---
## async_mtsdf_generator 设计
### 核心职责
1. 管理Worker线程池的生命周期
2. 调度MTSDF生成任务到空闲Worker
3. 处理任务完成回调
4. 支持任务取消
### Worker线程模型
每个Worker线程拥有
- 独立的 `FT_Library` 实例
- 独立的字体Face缓存从字体文件路径加载
- 专属的任务队列
```mermaid
graph LR
subgraph async_mtsdf_generator
TQ[Task Queue<br/>mpsc_queue]
Scheduler[Scheduler]
end
subgraph Worker 1
FT1[FT_Library]
FC1[Font Cache]
LQ1[Local Queue]
end
subgraph Worker 2
FT2[FT_Library]
FC2[Font Cache]
LQ2[Local Queue]
end
TQ --> Scheduler
Scheduler -->|Round Robin| LQ1
Scheduler -->|Round Robin| LQ2
```
### 任务定义
```cpp
struct mtsdf_task {
uint64_t task_id; // 唯一任务ID
std::filesystem::path font_path; // 字体文件路径
uint32_t glyph_index; // 字形索引
uint32_t glyph_size; // 输出尺寸
double pixel_range; // SDF范围
std::shared_ptr<async_handle<mtsdf_generator::glyph_bitmap>> handle;
};
```
### 任务调度策略
1. **Round-Robin**: 简单轮询分配到各Worker
2. **Work-Stealing**: Worker空闲时从其他队列偷取任务可选优化
---
## glyph_cache 改进
### 字形状态枚举
```cpp
enum class glyph_state : uint8_t {
ready, // 字形已生成,可直接使用
pending, // 生成中,返回占位符
failed // 生成失败使用fallback
};
```
### 缓存条目结构
```cpp
struct glyph_entry {
glyph_state state;
uint32_t glyph_id; // ready时有效
uint64_t task_id; // pending时有效用于取消
std::chrono::steady_clock::time_point request_time;
};
```
### 占位符机制
```mermaid
stateDiagram-v2
[*] --> NotCached: get_or_create
NotCached --> Pending: 提交异步任务
Pending --> Ready: 生成成功
Pending --> Failed: 生成失败
Failed --> Pending: 重试
Ready --> [*]
```
**占位符策略**:
- `pending` 状态返回 `nullptr` 或特殊占位符矩形
- 调用者可选择:跳过渲染 / 使用fallback字符 / 使用空白占位
- 完成后通过回调通知,下一帧自动更新
### 异步回调处理
```cpp
// 回调在Worker线程触发通过dispatcher调度到主线程
auto on_glyph_ready = [this, key](glyph_bitmap bitmap) {
get_dispatcher().dispatch_main([this, key, bmp = std::move(bitmap)] {
// 在主线程更新缓存和图集
finalize_glyph(key, bmp);
});
};
```
---
## 纹理上传批处理
### 设计原则
1. **收集**: 在一帧内收集所有完成的字形位图
2. **批量**: 帧开始时一次性上传所有新字形
3. **同步**: 使用staging buffer避免GPU同步开销
### 脏区域追踪
```cpp
struct dirty_region {
uint32_t x, y;
uint32_t width, height;
};
class font_atlas {
// ...
std::vector<dirty_region> dirty_regions_;
std::mutex atlas_mutex_;
};
```
### 上传流程
```mermaid
sequenceDiagram
participant Main as Main Thread
participant Atlas as font_atlas
participant Render as Render Thread
participant GPU as GPU
Note over Main: 帧N - 字形生成完成
Main->>Atlas: add_glyph x N
Atlas->>Atlas: 记录 dirty_regions
Note over Render: 帧N+1 开始
Render->>Atlas: get_dirty_regions
Atlas-->>Render: regions[]
Render->>GPU: 批量上传脏区域
Render->>Atlas: clear_dirty_flag
```
### 与渲染管线同步
```cpp
// 在render_pipeline或frame_scheduler中
void begin_frame() {
// 1. 处理主线程任务队列(包含字形完成回调)
dispatcher.process_render_queue();
// 2. 上传所有待处理的字形纹理
if (glyph_cache.get_atlas().is_dirty()) {
upload_atlas_updates();
}
// 3. 开始正常渲染
// ...
}
```
---
## 线程安全策略
### font_atlas 互斥锁保护
**需要保护的操作**:
- `add_glyph()`: 修改矩形打包状态和纹理数据
- `get_atlas_data()`: 读取纹理数据(上传时)
**不需要保护的操作**:
- `get_glyph_rect()`: 只读访问已存在的字形(原子性保证)
```cpp
class font_atlas {
public:
auto add_glyph(uint32_t id, const glyph_bitmap& bmp) -> bool {
std::lock_guard lock(mutex_);
// ... 添加字形逻辑
}
auto get_glyph_rect(uint32_t id) const -> const glyph_rect* {
// 读取不需要锁unordered_map的读取在无并发写入时安全
// 但需要确保在add_glyph完成后才能读取新添加的字形
return glyph_rects_.contains(id) ? &glyph_rects_.at(id) : nullptr;
}
private:
mutable std::mutex mutex_;
};
```
### 任务取消机制
```cpp
class async_mtsdf_generator {
public:
// 取消特定字形的生成任务
void cancel_task(uint64_t task_id) {
// 1. 从待处理队列中移除(如果还未开始)
pending_tasks_.erase(task_id);
// 2. 设置取消标志(如果正在执行)
if (auto handle = running_handles_.find(task_id); handle != running_handles_.end()) {
handle->second->cancel();
}
}
// 取消所有任务
void cancel_all() {
// ... 清理所有队列和运行中的任务
}
};
```
### 错误处理和恢复
| 错误类型 | 处理策略 |
|----------|----------|
| 字体加载失败 | 标记为 `failed`使用fallback字体 |
| MTSDF生成失败 | 重试一次,仍失败则使用占位符 |
| 图集空间不足 | 触发图集扩展或创建新图集 |
| Worker崩溃 | 重启Worker重新调度未完成任务 |
```cpp
enum class glyph_error {
none,
font_not_found,
glyph_not_found,
generation_failed,
atlas_full
};
struct glyph_result {
glyph_state state;
glyph_error error{glyph_error::none};
uint32_t glyph_id{0};
};
```
---
## 接口定义
### async_mtsdf_generator
```cpp
namespace mirage::render::text {
/// @brief 异步MTSDF生成器 - 管理后台线程池执行MTSDF生成
class async_mtsdf_generator {
public:
/// @brief 生成任务结果
using result_type = mtsdf_generator::glyph_bitmap;
/// @brief 任务句柄
using task_handle = threading::future_chain<result_type>;
/// @brief 配置参数
struct config {
uint32_t worker_count{std::thread::hardware_concurrency()};
uint32_t default_glyph_size{64};
double default_pixel_range{6.0};
std::chrono::milliseconds task_timeout{5000};
};
/// @brief 构造函数
/// @param cfg 配置参数
explicit async_mtsdf_generator(config cfg = {});
/// @brief 析构函数 - 停止所有Worker并等待完成
~async_mtsdf_generator();
// 禁止拷贝和移动
async_mtsdf_generator(const async_mtsdf_generator&) = delete;
async_mtsdf_generator& operator=(const async_mtsdf_generator&) = delete;
async_mtsdf_generator(async_mtsdf_generator&&) = delete;
async_mtsdf_generator& operator=(async_mtsdf_generator&&) = delete;
/// @brief 提交MTSDF生成任务
/// @param font_path 字体文件路径
/// @param glyph_index 字形索引
/// @param glyph_size 输出纹理尺寸
/// @param pixel_range SDF像素范围
/// @return 异步任务句柄
[[nodiscard]] auto submit(
const std::filesystem::path& font_path,
uint32_t glyph_index,
uint32_t glyph_size = 0, // 0 = 使用默认值
double pixel_range = 0.0 // 0 = 使用默认值
) -> task_handle;
/// @brief 批量提交任务
/// @param requests 任务请求列表
/// @return 任务句柄列表
[[nodiscard]] auto submit_batch(
std::span<const glyph_request> requests
) -> std::vector<task_handle>;
/// @brief 取消任务
/// @param task_id 任务ID
void cancel(uint64_t task_id);
/// @brief 取消所有待处理任务
void cancel_all_pending();
/// @brief 获取待处理任务数量
[[nodiscard]] auto pending_count() const noexcept -> size_t;
/// @brief 获取Worker数量
[[nodiscard]] auto worker_count() const noexcept -> size_t;
private:
struct impl;
std::unique_ptr<impl> pimpl_;
};
/// @brief 字形请求结构
struct glyph_request {
std::filesystem::path font_path;
uint32_t glyph_index;
uint32_t glyph_size{64};
double pixel_range{6.0};
};
} // namespace mirage::render::text
```
### mtsdf_worker
```cpp
namespace mirage::render::text {
/// @brief MTSDF Worker - 后台线程的执行上下文
class mtsdf_worker {
public:
/// @brief 构造Worker
/// @param worker_id 唯一Worker ID
explicit mtsdf_worker(uint32_t worker_id);
~mtsdf_worker();
// 禁止拷贝和移动
mtsdf_worker(const mtsdf_worker&) = delete;
mtsdf_worker& operator=(const mtsdf_worker&) = delete;
/// @brief 启动Worker线程
void start();
/// @brief 停止Worker线程
void stop();
/// @brief 提交任务到此Worker
/// @param task 任务数据
void submit(mtsdf_task task);
/// @brief 检查Worker是否空闲
[[nodiscard]] auto is_idle() const noexcept -> bool;
/// @brief 获取待处理任务数
[[nodiscard]] auto queue_size() const noexcept -> size_t;
private:
/// @brief Worker线程主循环
void run();
/// @brief 执行单个任务
/// @param task 任务数据
void execute_task(mtsdf_task& task);
/// @brief 加载字体(带缓存)
/// @param path 字体路径
/// @return FT_Face 或 nullptr
auto load_font_cached(const std::filesystem::path& path) -> FT_Face;
uint32_t worker_id_;
std::jthread thread_;
std::atomic<bool> running_{false};
std::atomic<bool> idle_{true};
// 每个Worker独立的FreeType资源
FT_Library ft_library_{nullptr};
std::unordered_map<std::string, FT_Face> font_cache_;
// 任务队列
threading::mpsc_queue<mtsdf_task> task_queue_;
};
} // namespace mirage::render::text
```
### glyph_cache 改进
```cpp
namespace mirage::render::text {
/// @brief 字形状态
enum class glyph_state : uint8_t {
ready, ///< 已生成,可使用
pending, ///< 生成中
failed ///< 生成失败
};
/// @brief 字形缓存 - 支持异步生成的字形管理
class glyph_cache {
public:
/// @brief 异步查询结果
struct async_result {
glyph_state state;
const font_atlas::glyph_rect* rect; // ready时有效
uint64_t task_id; // pending时有效
};
/// @brief 构造字形缓存
/// @param font_mgr 字体管理器(用于同步路径)
/// @param async_gen 异步生成器(用于异步路径)
/// @param atlas_size 图集大小
/// @param glyph_size MTSDF纹理大小
explicit glyph_cache(
font_manager& font_mgr,
async_mtsdf_generator& async_gen,
uint32_t atlas_size = 2048,
uint32_t glyph_size = 64
);
/// @brief 同步获取或创建字形(阻塞)
/// @param font 字体ID
/// @param codepoint Unicode码点
/// @return 字形矩形指针失败返回nullptr
[[nodiscard]] auto get_or_create_glyph(
font_manager::font_id_t font,
char32_t codepoint
) -> const font_atlas::glyph_rect*;
/// @brief 异步获取或创建字形(非阻塞)
/// @param font 字体ID
/// @param codepoint Unicode码点
/// @return 异步查询结果
[[nodiscard]] auto get_or_create_glyph_async(
font_manager::font_id_t font,
char32_t codepoint
) -> async_result;
/// @brief 预加载字形(异步)
/// @param font 字体ID
/// @param codepoints 要预加载的码点列表
void preload_glyphs(
font_manager::font_id_t font,
std::span<const char32_t> codepoints
);
/// @brief 处理完成的异步任务
/// 应在主线程每帧调用一次
void process_completed_tasks();
/// @brief 获取指定任务的状态
/// @param task_id 任务ID
/// @return 字形状态
[[nodiscard]] auto get_task_state(uint64_t task_id) const -> glyph_state;
/// @brief 取消待处理的任务
/// @param task_id 任务ID
void cancel_task(uint64_t task_id);
/// @brief 获取图集引用
[[nodiscard]] auto get_atlas() -> font_atlas&;
[[nodiscard]] auto get_atlas() const -> const font_atlas&;
/// @brief 获取字形纹理大小
[[nodiscard]] auto get_glyph_size() const -> uint32_t;
/// @brief 获取待处理任务数
[[nodiscard]] auto pending_count() const -> size_t;
private:
struct cache_entry {
glyph_state state{glyph_state::pending};
uint32_t glyph_id{0};
uint64_t task_id{0};
};
struct cache_key {
font_manager::font_id_t font;
uint32_t glyph_index;
auto operator==(const cache_key&) const -> bool = default;
};
struct cache_key_hash {
auto operator()(const cache_key& k) const -> size_t {
return (static_cast<uint64_t>(k.font) << 32) | k.glyph_index;
}
};
font_manager& font_mgr_;
async_mtsdf_generator& async_gen_;
font_atlas atlas_;
uint32_t glyph_size_;
std::unordered_map<cache_key, cache_entry, cache_key_hash> cache_;
std::unordered_map<uint64_t, cache_key> task_to_key_;
mutable std::shared_mutex cache_mutex_;
uint32_t next_glyph_id_{1};
uint64_t next_task_id_{1};
};
} // namespace mirage::render::text
```
### font_atlas 改进
```cpp
namespace mirage::render::text {
/// @brief 字体图集 - 线程安全版本
class font_atlas {
public:
/// @brief 脏区域信息
struct dirty_region {
uint32_t x, y;
uint32_t width, height;
};
explicit font_atlas(uint32_t atlas_size = 2048);
/// @brief 添加字形(线程安全)
/// @param glyph_id 字形ID
/// @param bitmap MTSDF位图
/// @return 成功返回true
[[nodiscard]] auto add_glyph(
uint32_t glyph_id,
const mtsdf_generator::glyph_bitmap& bitmap
) -> bool;
/// @brief 获取字形矩形(线程安全读取)
/// @param glyph_id 字形ID
/// @return 字形矩形指针
[[nodiscard]] auto get_glyph_rect(uint32_t glyph_id) const -> const glyph_rect*;
/// @brief 获取图集数据
[[nodiscard]] auto get_atlas_data() const -> const std::vector<uint8_t>&;
/// @brief 获取图集尺寸
[[nodiscard]] auto get_atlas_size() const -> uint32_t;
/// @brief 检查是否有待上传的更新
[[nodiscard]] auto is_dirty() const -> bool;
/// @brief 获取所有脏区域
[[nodiscard]] auto get_dirty_regions() const -> std::vector<dirty_region>;
/// @brief 清除脏标志和脏区域
void clear_dirty_flag();
/// @brief 获取总脏区域的包围盒
/// 用于优化单次大范围上传
[[nodiscard]] auto get_dirty_bounds() const -> std::optional<dirty_region>;
private:
// ... 现有私有成员 ...
mutable std::mutex mutex_; ///< 保护图集修改
std::vector<dirty_region> dirty_regions_; ///< 脏区域列表
};
} // namespace mirage::render::text
```
### texture_upload_manager
```cpp
namespace mirage::render::text {
/// @brief 纹理上传管理器 - 批量处理字形纹理上传
class texture_upload_manager {
public:
/// @brief 构造函数
/// @param atlas 字体图集引用
/// @param tex_mgr 纹理管理器引用
explicit texture_upload_manager(
font_atlas& atlas,
vulkan::texture_manager& tex_mgr
);
/// @brief 处理待上传的更新
/// 应在渲染线程帧开始时调用
void flush_pending_uploads();
/// @brief 强制完整上传图集
void force_full_upload();
/// @brief 获取图集纹理句柄
[[nodiscard]] auto get_atlas_texture() const -> VkImageView;
/// @brief 检查是否需要上传
[[nodiscard]] auto has_pending_uploads() const -> bool;
private:
font_atlas& atlas_;
vulkan::texture_manager& tex_mgr_;
// Staging buffer for efficient uploads
std::unique_ptr<vulkan::typed_buffer<uint8_t>> staging_buffer_;
// 图集纹理资源
VkImage atlas_image_{VK_NULL_HANDLE};
VkImageView atlas_view_{VK_NULL_HANDLE};
VmaAllocation atlas_allocation_{VK_NULL_HANDLE};
};
} // namespace mirage::render::text
```
---
## 使用示例
### 基本使用
```cpp
// 初始化
async_mtsdf_generator::config cfg{
.worker_count = 4,
.default_glyph_size = 64,
.default_pixel_range = 6.0
};
async_mtsdf_generator async_gen{cfg};
glyph_cache cache{font_mgr, async_gen};
// 异步请求字形
auto result = cache.get_or_create_glyph_async(font_id, U'A');
switch (result.state) {
case glyph_state::ready:
// 使用 result.rect 渲染
break;
case glyph_state::pending:
// 跳过或使用占位符
break;
case glyph_state::failed:
// 使用 fallback
break;
}
// 每帧处理完成的任务
void on_frame_begin() {
cache.process_completed_tasks();
upload_manager.flush_pending_uploads();
}
```
### 预加载字形
```cpp
// 预加载常用字符
std::vector<char32_t> ascii_chars;
for (char32_t c = 0x20; c < 0x7F; ++c) {
ascii_chars.push_back(c);
}
cache.preload_glyphs(font_id, ascii_chars);
```
### 与 future_chain 集成
```cpp
// 直接使用底层异步API
auto future = async_gen.submit(font_path, glyph_index);
future
.then([&cache, key](mtsdf_generator::glyph_bitmap bitmap) {
// 生成成功,添加到图集
cache.finalize_glyph(key, std::move(bitmap));
})
.on_error([key](threading::async_error err) {
// 生成失败标记为failed
spdlog::error("MTSDF generation failed for glyph {}", key.glyph_index);
})
.finally([] {
// 清理资源
});
```
---
## 性能考量
### 预期收益
| 场景 | 当前行为 | 优化后行为 |
|------|----------|------------|
| 首次渲染新字符 | 阻塞50-200ms | 异步,无阻塞 |
| 大量新字符 | 明显卡顿 | 渐进显示 |
| 后续帧 | 无影响 | 无影响 |
### 内存开销
- 每个Worker: ~10MBFreeType + 字体缓存)
- 4个Worker: ~40MB额外内存
- 任务队列: 可忽略
### 调优建议
1. **Worker数量**: 建议设为 `CPU核心数 / 2`,避免与渲染竞争
2. **任务超时**: 5秒足够处理复杂字形
3. **批量预加载**: 场景加载时预加载预期字符集
---
## 实现路线图
### 阶段1: 基础异步框架
- [ ] 实现 `mtsdf_worker`
- [ ] 实现 `async_mtsdf_generator` 线程池
- [ ] 单元测试验证基本功能
### 阶段2: 缓存集成
- [ ] 扩展 `glyph_cache` 支持异步
- [ ] 实现占位符机制
- [ ] 添加线程安全保护
### 阶段3: 渲染集成
- [ ] 实现 `texture_upload_manager`
- [ ] 集成到渲染管线
- [ ] 端到端测试
### 阶段4: 优化
- [ ] 任务优先级支持
- [ ] Work-stealing调度
- [ ] 性能分析和调优

View File

@@ -1,764 +0,0 @@
# 事件返回值重构设计文档
## 1. 概述
### 1.1 背景
当前事件系统使用 `void` 返回值,通过事件结构体中的 `mutable bool handled` 字段和手动调用 `router->capture_mouse(this)` 来控制事件行为。这种设计存在以下问题:
1. **隐式状态修改**事件处理的副作用handled 标志、鼠标捕获)散布在代码各处
2. **控件与路由器耦合**:控件需要获取 `event_router` 指针来捕获鼠标
3. **意图不明确**:需要阅读函数体才能知道事件是否被处理
4. **可组合性差**:难以在外部组合多个处理器的结果
### 1.2 设计目标
将事件处理的控制逻辑从"事件对象可变状态 + 手动调用 router"改为"函数返回值语义"
- **显式返回值**:通过返回值明确表达处理意图
- **解耦**:控件不再直接操作路由器,而是声明意图
- **可组合**:返回值可以被外部代码检查和组合
- **类型安全**:编译器强制处理返回值
---
## 2. 设计决策
### 2.1 统一的 event_result 类型
**决策**:所有事件类型共用一个 `event_result` 结构体。
**理由**
- 减少类型数量,代码更简洁
- 便于未来扩展新的标志位
- 使用 C++20 指定初始化语法保持可读性
- 结构体足够小(~1 字节),值传递无性能问题
**替代方案(已否决)**
- 为每种事件类型定义独立返回值类型(`mouse_event_result``keyboard_event_result` 等)
- 否决原因:增加类型数量,且大多数标志位是通用的
### 2.2 使用独立布尔字段而非位标志
**决策**:使用独立的 `bool` 字段而非 `uint8_t` 位标志。
**理由**
- 代码可读性更好:`result.handled` vs `result.flags & EVENT_HANDLED`
- 编译器通常会优化为相同的内存布局
- C++20 的指定初始化语法与布尔字段配合更自然
---
## 3. 类型定义
### 3.1 event_result 结构体
```cpp
// 文件src/widget/widget_event/event_result.h
#pragma once
namespace mirage {
/// @brief 事件处理结果
///
/// 控件事件处理方法的返回值,用于向事件路由器传达处理意图。
/// 路由器根据返回值执行相应操作(停止冒泡、捕获鼠标等)。
///
/// @note 使用静态工厂方法创建常用的结果对象
struct event_result {
/// 事件是否已处理(停止冒泡)
bool handled{false};
/// 请求捕获鼠标(用于拖拽场景)
bool capture_mouse{false};
/// 请求释放鼠标捕获
bool release_mouse_capture{false};
// ========================================================================
// 静态工厂方法
// ========================================================================
/// @brief 创建未处理的结果
/// @details 事件将继续冒泡到父控件
[[nodiscard]] static constexpr event_result unhandled() noexcept {
return {};
}
/// @brief 创建已处理的结果
/// @details 停止事件冒泡,但不执行其他操作
[[nodiscard]] static constexpr event_result handled() noexcept {
return {.handled = true};
}
/// @brief 创建捕获鼠标的结果
/// @details 停止冒泡并请求路由器捕获鼠标到当前控件
[[nodiscard]] static constexpr event_result capture() noexcept {
return {
.handled = true,
.capture_mouse = true
};
}
/// @brief 创建释放鼠标捕获的结果
/// @details 停止冒泡并请求路由器释放鼠标捕获
[[nodiscard]] static constexpr event_result release() noexcept {
return {
.handled = true,
.release_mouse_capture = true
};
}
// ========================================================================
// 组合操作
// ========================================================================
/// @brief 合并两个结果
/// @details OR 语义:任一标志为 true 则结果为 true
[[nodiscard]] constexpr event_result merge_with(const event_result& other) const noexcept {
return {
.handled = handled || other.handled,
.capture_mouse = capture_mouse || other.capture_mouse,
.release_mouse_capture = release_mouse_capture || other.release_mouse_capture
};
}
/// @brief 检查是否有任何效果
[[nodiscard]] constexpr bool has_effect() const noexcept {
return handled || capture_mouse || release_mouse_capture;
}
};
} // namespace mirage
```
### 3.2 内存布局分析
```cpp
// event_result 的预期内存布局
//
// 字段布局(假设无填充优化):
// - handled: 1 byte
// - capture_mouse: 1 byte
// - release_mouse_capture: 1 byte
// 总计3 bytes可能填充到 4 bytes
//
// 如果使用 bitfield 压缩:
// struct event_result {
// bool handled : 1;
// bool capture_mouse : 1;
// bool release_mouse_capture : 1;
// };
// 总计1 byte
//
// 考虑到现代 CPU 的寄存器传递能力,
// 3-4 bytes 的值类型传递成本可忽略不计。
```
---
## 4. 事件处理器接口
### 4.1 新的签名
```cpp
// 文件src/widget/widget_event/event_target.h
class event_target {
public:
// ========================================================================
// 键盘事件处理(返回 event_result
// ========================================================================
/// @brief 键按下事件
/// @param event 键盘事件对象(只读)
/// @return 事件处理结果
[[nodiscard]] virtual event_result on_key_down(const keyboard_event& event) {
(void)event;
return event_result::unhandled();
}
/// @brief 键释放事件
[[nodiscard]] virtual event_result on_key_up(const keyboard_event& event) {
(void)event;
return event_result::unhandled();
}
/// @brief 字符输入事件
[[nodiscard]] virtual event_result on_char_input(const char_event& event) {
(void)event;
return event_result::unhandled();
}
// ========================================================================
// 鼠标事件处理(返回 event_result
// ========================================================================
/// @brief 鼠标按下事件
[[nodiscard]] virtual event_result on_mouse_down(const mouse_event& event) {
(void)event;
return event_result::unhandled();
}
/// @brief 鼠标释放事件
[[nodiscard]] virtual event_result on_mouse_up(const mouse_event& event) {
(void)event;
return event_result::unhandled();
}
/// @brief 鼠标移动事件
[[nodiscard]] virtual event_result on_mouse_move(const mouse_event& event) {
(void)event;
return event_result::unhandled();
}
/// @brief 鼠标滚轮事件
[[nodiscard]] virtual event_result on_mouse_scroll(const mouse_event& event) {
(void)event;
return event_result::unhandled();
}
// ... 其他方法保持不变
};
```
### 4.2 事件结构体修改
从事件结构体中移除 `handled` 字段和 `stop_propagation()` 方法:
```cpp
// 文件src/widget/widget_event/event_types.h
struct widget_keyboard_event {
key_action action{key_action::press};
key_code key{key_code::UNKNOWN};
int32_t scancode{0};
key_mod modifiers{key_mod::NONE};
bool repeat{false};
// 移除以下内容:
// mutable bool handled{false};
// void stop_propagation() const noexcept { handled = true; }
// 辅助方法保持不变
[[nodiscard]] bool has_modifier(key_mod mod) const noexcept;
[[nodiscard]] bool ctrl() const noexcept;
[[nodiscard]] bool shift() const noexcept;
[[nodiscard]] bool alt() const noexcept;
[[nodiscard]] bool super() const noexcept;
};
struct widget_mouse_event {
double global_x{0.0};
double global_y{0.0};
mouse_button button{mouse_button::LEFT};
key_mod modifiers{key_mod::NONE};
double scroll_x{0.0};
double scroll_y{0.0};
// 移除以下内容:
// mutable bool handled{false};
// void stop_propagation() const noexcept { handled = true; }
};
struct widget_char_event {
uint32_t codepoint{0};
key_mod modifiers{key_mod::NONE};
// 移除以下内容:
// mutable bool handled{false};
// void stop_propagation() const noexcept { handled = true; }
};
```
---
## 5. 路由器修改
### 5.1 鼠标事件处理
```cpp
// 文件src/widget/widget_event/widget_event_router.cpp
void widget_event_router::handle_mouse_event(
event_type type,
double x, double y,
mouse_button button,
key_mod mods,
double scroll_x,
double scroll_y
) {
if (!root_target_) return;
// 构建事件对象(不再包含 handled 字段)
mouse_event event{
.global_x = x,
.global_y = y,
.button = button,
.modifiers = mods,
.scroll_x = scroll_x,
.scroll_y = scroll_y
};
// 处理鼠标捕获
if (mouse_capture_) {
if (mouse_capture_->is_event_enabled()) {
auto result = dispatch_to_captured_target(type, event);
process_event_result(result, mouse_capture_);
return;
}
mouse_capture_ = nullptr;
}
// 命中测试
event_target* hit_target = hit_test(root_target_, x, y);
if (!hit_target) return;
// 分发事件并处理返回值
auto result = dispatch_mouse_event(type, hit_target, event);
process_event_result(result, hit_target);
}
// 新增:处理事件结果的私有方法
void widget_event_router::process_event_result(
const event_result& result,
event_target* target
) {
if (result.capture_mouse && target) {
mouse_capture_ = target;
}
if (result.release_mouse_capture) {
mouse_capture_ = nullptr;
}
// 注意handled 标志在冒泡循环中处理
}
// 新增:分发鼠标事件的私有方法
event_result widget_event_router::dispatch_mouse_event(
event_type type,
event_target* target,
const mouse_event& event
) {
switch (type) {
case event_type::MOUSE_BUTTON_PRESSED:
// 处理自动聚焦
if (event.button == mouse_button::LEFT && target->is_focusable()) {
focus_manager_.set_focus(target, focus_change_reason::mouse_click);
}
return dispatch_with_bubbling(target, [&](event_target* t) {
return t->on_mouse_down(event);
});
case event_type::MOUSE_BUTTON_RELEASED:
return dispatch_with_bubbling(target, [&](event_target* t) {
return t->on_mouse_up(event);
});
case event_type::MOUSE_MOVED:
return dispatch_with_bubbling(target, [&](event_target* t) {
return t->on_mouse_move(event);
});
case event_type::MOUSE_SCROLLED:
return dispatch_with_bubbling(target, [&](event_target* t) {
return t->on_mouse_scroll(event);
});
default:
return event_result::unhandled();
}
}
```
### 5.2 通用冒泡分发模板
```cpp
// 文件src/widget/widget_event/widget_event_router.h私有方法声明
// 文件src/widget/widget_event/widget_event_router.cpp实现
/// @brief 带冒泡的事件分发
/// @tparam Handler 事件处理函数类型
/// @param target 起始目标控件
/// @param handler 处理函数 (event_target*) -> event_result
/// @return 最终的事件处理结果
template<typename Handler>
event_result widget_event_router::dispatch_with_bubbling(
event_target* target,
Handler&& handler
) {
event_result final_result = event_result::unhandled();
for (auto* current = target; current != nullptr; current = current->get_parent_target()) {
if (!current->is_event_enabled()) {
continue;
}
auto result = handler(current);
final_result = final_result.merge_with(result);
// 如果已处理,停止冒泡
if (result.handled) {
break;
}
}
return final_result;
}
```
### 5.3 键盘事件处理修改
```cpp
void widget_event_router::dispatch_keyboard_event(const keyboard_event& event) {
const auto& chain = focus_manager_.get_focus_chain();
if (chain.empty()) return;
// 从焦点控件开始向上冒泡
for (auto* target : chain | std::views::reverse) {
if (!target || !target->is_event_enabled()) {
continue;
}
event_result result;
switch (event.action) {
case key_action::press:
case key_action::repeat:
result = target->on_key_down(event);
break;
case key_action::release:
result = target->on_key_up(event);
break;
}
if (result.handled) {
break;
}
}
}
```
---
## 6. 代码对比示例
### 6.1 Scrollbar 控件Before
```cpp
// 当前实现 (scrollbar.cpp)
void scrollbar::on_mouse_down(mouse_event& event) {
if (event.handled) return; // 检查是否已处理
auto local_pos = global_to_local(event.global_x, event.global_y);
float local_x = static_cast<float>(local_pos.first);
float local_y = static_cast<float>(local_pos.second);
if (is_point_in_thumb(local_x, local_y)) {
is_dragging_thumb_ = true;
is_thumb_active_ = true;
drag_start_pos_ = vec2f_t{local_x, local_y};
drag_start_value_ = current_value_;
// 手动获取路由器并捕获鼠标
if (auto* router = get_event_router()) {
router->capture_mouse(this);
}
if (drag_state_changed_callback_) {
drag_state_changed_callback_(true);
}
event.handled = true; // 设置已处理标志
}
}
void scrollbar::on_mouse_up(mouse_event& event) {
if (event.handled) return;
if (is_dragging_thumb_) {
is_dragging_thumb_ = false;
is_thumb_active_ = false;
// 手动获取路由器并释放捕获
if (auto* router = get_event_router()) {
router->release_mouse_capture();
}
if (drag_state_changed_callback_) {
drag_state_changed_callback_(false);
}
event.handled = true;
}
}
```
### 6.2 Scrollbar 控件After
```cpp
// 重构后的实现 (scrollbar.cpp)
event_result scrollbar::on_mouse_down(const mouse_event& event) {
auto local_pos = global_to_local(event.global_x, event.global_y);
float local_x = static_cast<float>(local_pos.first);
float local_y = static_cast<float>(local_pos.second);
if (is_point_in_thumb(local_x, local_y)) {
is_dragging_thumb_ = true;
is_thumb_active_ = true;
drag_start_pos_ = vec2f_t{local_x, local_y};
drag_start_value_ = current_value_;
if (drag_state_changed_callback_) {
drag_state_changed_callback_(true);
}
// 返回值明确表达意图:已处理 + 捕获鼠标
return event_result::capture();
}
return event_result::unhandled();
}
event_result scrollbar::on_mouse_up(const mouse_event& event) {
if (is_dragging_thumb_) {
is_dragging_thumb_ = false;
is_thumb_active_ = false;
if (drag_state_changed_callback_) {
drag_state_changed_callback_(false);
}
// 返回值明确表达意图:已处理 + 释放捕获
return event_result::release();
}
return event_result::unhandled();
}
event_result scrollbar::on_mouse_move(const mouse_event& event) {
auto local_pos = global_to_local(event.global_x, event.global_y);
float local_x = static_cast<float>(local_pos.first);
float local_y = static_cast<float>(local_pos.second);
if (is_dragging_thumb_) {
handle_thumb_drag(local_x, local_y);
if (drag_state_changed_callback_) {
drag_state_changed_callback_(true);
}
return event_result::handled();
}
bool was_hovered = is_thumb_hovered_;
is_thumb_hovered_ = is_point_in_thumb(local_x, local_y);
if (was_hovered != is_thumb_hovered_) {
mark_render_dirty();
}
// hover 状态变化不消耗事件
return event_result::unhandled();
}
```
### 6.3 改进点总结
| 方面 | Before | After |
|------|--------|-------|
| 事件处理检查 | `if (event.handled) return;` | 不需要,由路由器管理 |
| 标记已处理 | `event.handled = true;` | `return event_result::handled();` |
| 鼠标捕获 | `router->capture_mouse(this);` | `return event_result::capture();` |
| 鼠标释放 | `router->release_mouse_capture();` | `return event_result::release();` |
| 意图表达 | 散布在函数体内 | 集中在返回值 |
| 与路由器耦合 | 需要 `get_event_router()` | 无直接依赖 |
---
## 7. 迁移指南
### 7.1 迁移步骤
#### 步骤 1创建 event_result.h
`src/widget/widget_event/` 下创建 `event_result.h`,包含 `event_result` 结构体定义。
#### 步骤 2修改 event_types.h
1. 从所有事件结构体中移除 `mutable bool handled` 字段
2. 移除 `stop_propagation()` 方法
3. 在文件头部包含 `event_result.h`
#### 步骤 3修改 event_target.h
1. 包含 `event_result.h`
2. 修改所有事件处理方法签名:
- 返回类型从 `void` 改为 `event_result`
- 参数从 `T&` 改为 `const T&`
- 添加 `[[nodiscard]]` 属性
3. 修改默认实现返回 `event_result::unhandled()`
#### 步骤 4修改 widget_event_router.cpp
1. 修改事件分发逻辑,检查返回值
2. 实现 `process_event_result()` 方法
3. 更新冒泡逻辑使用返回值的 `handled` 标志
#### 步骤 5批量更新控件
使用搜索替换更新所有控件的事件处理方法。
### 7.2 搜索替换模式
```
# 查找带 handled 检查的模式
搜索if \(event\.handled\) return;
替换:(删除整行)
# 查找 event.handled = true
搜索event\.handled = true;
替换return event_result::handled();
# 查找 event.stop_propagation()
搜索event\.stop_propagation\(\);
替换return event_result::handled();
# 查找 router->capture_mouse(this)
搜索if \(auto\* router = get_event_router\(\)\) \{\s*router->capture_mouse\(this\);\s*\}
替换:// 在返回时使用 event_result::capture()
# 查找函数签名
搜索void on_mouse_down\(mouse_event& event\)
替换event_result on_mouse_down(const mouse_event& event)
```
### 7.3 编译时检查
重构完成后,编译器会自动报告:
- 未更新的事件处理方法签名(参数类型不匹配)
- 仍在访问已删除的 `handled` 字段
- 返回值未返回 `event_result`
---
## 8. 测试计划
### 8.1 单元测试
```cpp
// 测试 event_result 工厂方法
TEST_CASE("event_result factory methods") {
SECTION("unhandled") {
auto r = event_result::unhandled();
CHECK(!r.handled);
CHECK(!r.capture_mouse);
CHECK(!r.release_mouse_capture);
}
SECTION("handled") {
auto r = event_result::handled();
CHECK(r.handled);
CHECK(!r.capture_mouse);
}
SECTION("capture") {
auto r = event_result::capture();
CHECK(r.handled);
CHECK(r.capture_mouse);
}
SECTION("release") {
auto r = event_result::release();
CHECK(r.handled);
CHECK(r.release_mouse_capture);
}
}
// 测试 merge 行为
TEST_CASE("event_result merge") {
auto a = event_result::handled();
auto b = event_result{.capture_mouse = true};
auto merged = a.merge_with(b);
CHECK(merged.handled);
CHECK(merged.capture_mouse);
}
```
### 8.2 集成测试
1. **拖拽测试**:验证 scrollbar 拖拽仍能正确捕获/释放鼠标
2. **冒泡测试**:验证事件在嵌套控件中正确冒泡
3. **焦点测试**:验证鼠标点击仍能正确设置焦点
---
## 9. 未来扩展
### 9.1 可能的新标志位
```cpp
struct event_result {
bool handled{false};
bool capture_mouse{false};
bool release_mouse_capture{false};
// 未来可能添加:
// bool request_focus{false}; // 请求焦点
// bool prevent_default{false}; // 阻止默认行为
// bool stop_immediate{false}; // 立即停止,不传给同级处理器
// cursor_type cursor{cursor_type::default}; // 请求改变光标
};
```
### 9.2 链式处理
```cpp
// 未来可能支持链式处理器
auto result = handlers
| handle_if_focused()
| handle_shortcuts()
| handle_default();
```
---
## 10. 文件变更清单
| 文件路径 | 操作 | 说明 |
|---------|------|------|
| `src/widget/widget_event/event_result.h` | 新建 | event_result 结构体定义 |
| `src/widget/widget_event/event_types.h` | 修改 | 移除 handled 字段和 stop_propagation |
| `src/widget/widget_event/event_target.h` | 修改 | 修改方法签名 |
| `src/widget/widget_event/widget_event_router.h` | 修改 | 添加私有方法声明 |
| `src/widget/widget_event/widget_event_router.cpp` | 修改 | 重写事件分发逻辑 |
| `src/widget/widgets/scrollbar.cpp` | 修改 | 更新事件处理实现 |
| `src/widget/widgets/*.cpp` | 修改 | 更新所有控件的事件处理 |
---
## 11. 实现顺序
```mermaid
flowchart TD
A[1. 创建 event_result.h] --> B[2. 修改 event_types.h]
B --> C[3. 修改 event_target.h]
C --> D[4. 修改 widget_event_router]
D --> E[5. 更新 scrollbar]
E --> F[6. 更新其他控件]
F --> G[7. 编写测试]
G --> H[8. 代码审查]
```
---
## 附录 A完整 event_result.h 源码
参见第 3.1 节。
## 附录 B相关设计文档
- [`KEYBOARD_EVENT_SYSTEM.md`](./KEYBOARD_EVENT_SYSTEM.md) - 键盘事件系统设计
- [`WIDGET_THREAD_SYNC_REFACTORING_PLAN.md`](./WIDGET_THREAD_SYNC_REFACTORING_PLAN.md) - 控件线程同步重构

View File

@@ -1,664 +0,0 @@
# Event Target 消除重构方案
## 1. 背景与问题
### 1.1 当前架构
当前代码中,`event_target` 是一个独立的接口类,控件需要同时继承 `widget_base`(或其派生类如 `container_widget_base``leaf_widget_base`)和 `event_target`
```mermaid
classDiagram
class widget_base {
+get_parent()
+set_parent()
+as_event_target()
+get_parent_event_target()
}
class event_target {
+on_mouse_down()
+on_mouse_scroll()
+get_parent_target()
+get_event_children()
+contains_point()
}
class container_widget_base {
+add_child()
+get_event_children()
}
class overlay {
缺少 get_parent_target 实现
}
class scroll_box {
+on_mouse_scroll()
+get_parent_target()
}
widget_base <|-- container_widget_base
container_widget_base <|-- overlay
container_widget_base <|-- scroll_box
event_target <|-- overlay
event_target <|-- scroll_box
```
### 1.2 问题描述
1. **事件冒泡链断裂**`overlay` 控件继承了 `event_target`,但没有重写 [`get_parent_target()`](src/widget/widget_event/event_target.h:272) 方法。默认实现返回 `nullptr`,导致滚轮事件无法从子控件冒泡到父级 `scroll_box`
2. **多重继承复杂性**:每个需要事件处理的控件都必须同时继承两个基类,增加了代码复杂度和出错可能性。
3. **可视性状态重复**
- `widget_base` 通过外部状态存储管理 `visibility`
- `event_target` 有自己的 [`visibility_`](src/widget/widget_event/event_target.h:365) 成员变量
- 这导致状态不一致的风险
4. **接口分散**:父子关系由 `widget_base` 管理(`parent_` 指针),但事件冒泡需要 `event_target::get_parent_target()`,两者需要手动保持同步。
### 1.3 问题根源
滚轮事件分发流程([`widget_event_router.cpp:377-400`](src/widget/widget_event/widget_event_router.cpp:377)
```cpp
void widget_event_router::dispatch_scroll_event(event_target* target, mouse_event& event) {
event_target* current = target;
while (current) {
if (current->is_event_enabled()) {
auto result = current->on_mouse_scroll(event);
if (result.is_handled) {
break;
}
}
// 问题在这里:如果 overlay 的 get_parent_target() 返回 nullptr
// 冒泡链就断裂了scroll_box 永远收不到滚轮事件
current = current->get_parent_target();
}
}
```
## 2. 解决方案设计
### 2.1 核心思路
`event_target` 的所有接口方法整合到 `widget_base` 中,利用 `widget_base` 已有的 `parent_` 指针自动提供 `get_parent_target()` 的默认实现。
### 2.2 新的类层次结构
```mermaid
classDiagram
class widget_base {
#parent_: widget_base*
#event_router_: widget_event_router*
#visibility_: visibility
+get_parent(): widget_base*
+set_parent(widget_base*)
+on_key_down(keyboard_event): event_result
+on_key_up(keyboard_event): event_result
+on_char_input(char_event): event_result
+on_mouse_down(mouse_event): event_result
+on_mouse_up(mouse_event): event_result
+on_mouse_move(mouse_event): event_result
+on_mouse_scroll(mouse_event): event_result
+on_focus_gained(focus_change_reason)
+on_focus_lost(focus_change_reason)
+on_ime_preedit_start(ime_preedit_event)
+on_ime_preedit_update(ime_preedit_event)
+on_ime_preedit_end(ime_preedit_event)
+on_ime_commit(ime_commit_event)
+is_focusable(): bool
+get_tab_index(): int32_t
+get_parent_target(): widget_base*
+supports_ime(): bool
+get_ime_cursor_position(): tuple
+is_event_enabled(): bool
+contains_point(x, y): bool
+global_to_local(x, y): pair
+get_event_children(): vector
+set_event_router(router)
+get_event_router(): widget_event_router*
}
class container_widget_base {
+get_event_children(): vector
}
class leaf_widget_base {
}
class overlay {
继承后自动获得正确的 get_parent_target
}
class scroll_box {
+on_mouse_scroll(): event_result
}
widget_base <|-- container_widget_base
widget_base <|-- leaf_widget_base
container_widget_base <|-- overlay
container_widget_base <|-- scroll_box
```
### 2.3 新的 widget_base 接口设计
#### 2.3.1 事件处理方法(从 event_target 移入)
```cpp
class widget_base : public std::enable_shared_from_this<widget_base> {
public:
// ========================================================================
// 键盘事件处理(新增,从 event_target 移入)
// ========================================================================
[[nodiscard]] virtual event_result on_key_down(const keyboard_event& event) {
(void)event;
return event_result::unhandled();
}
[[nodiscard]] virtual event_result on_key_up(const keyboard_event& event) {
(void)event;
return event_result::unhandled();
}
[[nodiscard]] virtual event_result on_char_input(const char_event& event) {
(void)event;
return event_result::unhandled();
}
// ========================================================================
// 鼠标事件处理(新增,从 event_target 移入)
// ========================================================================
[[nodiscard]] virtual event_result on_mouse_down(const mouse_event& event) {
(void)event;
return event_result::unhandled();
}
[[nodiscard]] virtual event_result on_mouse_up(const mouse_event& event) {
(void)event;
return event_result::unhandled();
}
[[nodiscard]] virtual event_result on_mouse_move(const mouse_event& event) {
(void)event;
return event_result::unhandled();
}
[[nodiscard]] virtual event_result on_mouse_scroll(const mouse_event& event) {
(void)event;
return event_result::unhandled();
}
// ========================================================================
// 焦点事件处理(新增,从 event_target 移入)
// ========================================================================
virtual void on_focus_gained(focus_change_reason reason) {
(void)reason;
}
virtual void on_focus_lost(focus_change_reason reason) {
(void)reason;
}
// ========================================================================
// IME 事件处理(新增,从 event_target 移入)
// ========================================================================
virtual void on_ime_preedit_start(ime_preedit_event& event) {
(void)event;
}
virtual void on_ime_preedit_update(ime_preedit_event& event) {
(void)event;
}
virtual void on_ime_preedit_end(ime_preedit_event& event) {
(void)event;
}
virtual void on_ime_commit(ime_commit_event& event) {
(void)event;
}
// ========================================================================
// 焦点属性(新增,从 event_target 移入)
// ========================================================================
[[nodiscard]] virtual bool is_focusable() const {
return false;
}
[[nodiscard]] virtual int32_t get_tab_index() const {
return -1;
}
// ========================================================================
// 事件冒泡支持(关键改进)
// ========================================================================
/// @brief 获取父事件目标(用于事件冒泡)
/// @return 父控件指针,自动使用 parent_ 成员
/// @note 这是解决问题的关键:默认实现直接返回 parent_
[[nodiscard]] virtual widget_base* get_parent_target() const {
return parent_;
}
// ========================================================================
// IME 支持(新增,从 event_target 移入)
// ========================================================================
[[nodiscard]] virtual bool supports_ime() const {
return false;
}
[[nodiscard]] virtual std::tuple<int32_t, int32_t, int32_t>
get_ime_cursor_position() const {
return {0, 0, 20};
}
// ========================================================================
// 事件启用状态(新增,从 event_target 移入)
// ========================================================================
[[nodiscard]] virtual bool is_event_enabled() const {
return true;
}
// ========================================================================
// 命中测试(新增,从 event_target 移入)
// ========================================================================
[[nodiscard]] virtual bool contains_point(double global_x, double global_y) const {
// 默认实现:使用布局状态检查
auto state = get_layout_state();
if (!state) return false;
return state->contains_global_point(
vec2f_t(static_cast<float>(global_x), static_cast<float>(global_y)));
}
[[nodiscard]] virtual std::pair<double, double> global_to_local(
double global_x, double global_y) const {
auto state = get_layout_state();
if (!state) return {0.0, 0.0};
auto local = state->to_local(
vec2f_t(static_cast<float>(global_x), static_cast<float>(global_y)));
return {local.x(), local.y()};
}
// ========================================================================
// 事件子控件(新增,从 event_target 移入)
// ========================================================================
[[nodiscard]] virtual std::vector<widget_base*> get_event_children() const {
return {};
}
// ========================================================================
// 事件路由器(新增,从 event_target 移入)
// ========================================================================
void set_event_router(widget_event_router* router) noexcept {
event_router_ = router;
}
[[nodiscard]] widget_event_router* get_event_router() const noexcept {
return event_router_;
}
protected:
widget_event_router* event_router_{nullptr};
private:
// as_event_target() 和 get_parent_event_target() 可以删除
// 因为 widget_base 自己就是事件目标
};
```
#### 2.3.2 需要删除的方法
`widget_base` 中删除以下方法,因为不再需要:
```cpp
// 删除widget_base 自己就是事件目标
[[nodiscard]] virtual auto as_event_target() -> event_target* { ... }
// 删除:被 get_parent_target() 替代
[[nodiscard]] virtual auto get_parent_event_target() const -> event_target* { ... }
```
### 2.4 container_widget_base 的修改
```cpp
class container_widget_base : public widget_base {
public:
// ========================================================================
// 事件子控件获取(重写基类方法)
// ========================================================================
/// @brief 获取事件子控件列表
/// @return 返回 widget_base* 而不是 event_target*
[[nodiscard]] std::vector<widget_base*> get_event_children() const override {
std::vector<widget_base*> targets;
targets.reserve(children_.size());
for (const auto& child : children_) {
targets.push_back(child.get());
}
return targets;
}
// 删除旧的返回 event_target* 的版本
};
```
### 2.5 事件系统的修改
#### 2.5.1 widget_event_router 修改
将所有 `event_target*` 替换为 `widget_base*`
```cpp
class widget_event_router {
public:
void set_root(widget_base* root) noexcept;
[[nodiscard]] widget_base* get_root() const noexcept;
void capture_mouse(widget_base* target);
[[nodiscard]] widget_base* get_mouse_capture() const noexcept;
private:
widget_base* root_target_{nullptr};
widget_base* mouse_capture_{nullptr};
[[nodiscard]] widget_base* hit_test(widget_base* target, double x, double y);
void dispatch_scroll_event(widget_base* target, mouse_event& event);
void dispatch_mouse_event_to_target(widget_base* target, mouse_event& event);
};
```
#### 2.5.2 focus_manager 修改
```cpp
class focus_manager {
public:
bool set_focus(widget_base* target, focus_change_reason reason);
[[nodiscard]] widget_base* get_focused_widget() const noexcept;
private:
widget_base* focused_widget_{nullptr};
std::vector<widget_base*> focus_chain_;
};
```
## 3. 需要修改的文件清单
### 3.1 核心文件(必须修改)
| 文件 | 修改类型 | 说明 |
|------|----------|------|
| [`src/widget/widget_base.h`](src/widget/widget_base.h) | 重大修改 | 整合 event_target 所有接口 |
| [`src/widget/widget_event/event_target.h`](src/widget/widget_event/event_target.h) | **删除** | 迁移后删除整个文件 |
| [`src/widget/container_widget_base.h`](src/widget/container_widget_base.h) | 修改 | 更新 `get_event_children()` 返回类型 |
| [`src/widget/composite_widget_base.h`](src/widget/composite_widget_base.h) | 修改 | 删除 event_target 相关代码 |
| [`src/widget/leaf_widget_base.h`](src/widget/leaf_widget_base.h) | 小修改 | 确保兼容性 |
### 3.2 事件系统文件(必须修改)
| 文件 | 修改类型 | 说明 |
|------|----------|------|
| [`src/widget/widget_event/widget_event_router.h`](src/widget/widget_event/widget_event_router.h) | 修改 | `event_target*``widget_base*` |
| [`src/widget/widget_event/widget_event_router.cpp`](src/widget/widget_event/widget_event_router.cpp) | 修改 | 同上 |
| [`src/widget/widget_event/focus_manager.h`](src/widget/widget_event/focus_manager.h) | 修改 | `event_target*``widget_base*` |
| [`src/widget/widget_event/focus_manager.cpp`](src/widget/widget_event/focus_manager.cpp) | 修改 | 同上 |
| [`src/widget/widget_event/ime_manager.h`](src/widget/widget_event/ime_manager.h) | 修改 | 可能需要更新类型 |
| [`src/widget/widget_event/shortcut_manager.h`](src/widget/widget_event/shortcut_manager.h) | 检查 | 可能需要更新类型 |
### 3.3 容器控件文件(删除多重继承)
| 文件 | 修改类型 | 说明 |
|------|----------|------|
| [`src/widget/containers/overlay.h`](src/widget/containers/overlay.h) | 修改 | 删除 `: public event_target` |
| [`src/widget/containers/scroll_box/scroll_box.h`](src/widget/containers/scroll_box/scroll_box.h) | 修改 | 删除 `: public event_target` |
| [`src/widget/containers/v_stack.h`](src/widget/containers/v_stack.h) | 修改 | 删除 `: public event_target` |
| [`src/widget/containers/h_stack.h`](src/widget/containers/h_stack.h) | 修改 | 删除 `: public event_target` |
### 3.4 叶子控件文件(删除多重继承)
| 文件 | 修改类型 | 说明 |
|------|----------|------|
| [`src/widget/widgets/scrollbar.h`](src/widget/widgets/scrollbar.h) | 修改 | 删除 `: public event_target` |
| 其他叶子控件 | 检查 | 检查是否有其他继承 event_target 的控件 |
### 3.5 其他可能受影响的文件
| 文件 | 修改类型 | 说明 |
|------|----------|------|
| `src/widget/CMakeLists.txt` | 可能修改 | 删除 event_target.h |
| `src/app/application.cpp` | 检查 | 检查 event_target 使用 |
## 4. 迁移策略
### 4.1 第一阶段:准备工作
**目标**:在不破坏现有功能的情况下,为迁移做准备
1. **添加类型别名**(保持向后兼容):
```cpp
// 在 widget_base.h 中添加
// 临时别名,方便逐步迁移
namespace mirage {
class widget_base;
using event_target_t = widget_base; // 新代码使用这个
}
```
2. **在 widget_base 中添加所有 event_target 的方法**(作为虚函数,默认实现)
3. **保留 event_target 类**,但标记为废弃
### 4.2 第二阶段:更新事件系统
**目标**:让事件系统使用 widget_base* 而不是 event_target*
1. 修改 `widget_event_router`
- 所有 `event_target*` 参数改为 `widget_base*`
- 更新命中测试逻辑
- 更新事件分发逻辑
2. 修改 `focus_manager`
- 焦点链使用 `widget_base*`
- 更新焦点切换逻辑
3. 修改 `ime_manager`(如需要)
### 4.3 第三阶段:更新控件类
**目标**:移除所有控件的 event_target 继承
1. **容器控件**(按依赖顺序):
```cpp
// overlay.h - 修改前
class overlay : public container_widget_base, public event_target { ... }
// overlay.h - 修改后
class overlay : public container_widget_base { ... }
```
2. **叶子控件**
```cpp
// scrollbar.h - 修改前
class scrollbar : public leaf_widget_base, public event_target { ... }
// scrollbar.h - 修改后
class scrollbar : public leaf_widget_base { ... }
```
3. **移除各控件中重复的方法**
- 删除 `as_event_target()` 重写
- 删除 `get_parent_target()` 重写(如果只是返回父控件)
- 保留有特殊逻辑的事件处理方法
### 4.4 第四阶段:清理
**目标**:删除废弃代码
1. 删除 `event_target.h` 文件
2. 从 `widget_base.h` 中删除 `as_event_target()` 方法
3. 从 `widget_base.h` 中删除 `get_parent_event_target()` 方法
4. 更新 CMakeLists.txt
5. 运行全量测试
## 5. 风险与注意事项
### 5.1 潜在风险
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| 破坏现有功能 | 高 | 分阶段迁移,每阶段完成后充分测试 |
| 编译错误 | 中 | 使用类型别名逐步过渡 |
| 运行时错误 | 中 | 保持默认实现与原有行为一致 |
| 性能影响 | 低 | 虚函数调用开销可忽略 |
### 5.2 关键注意事项
1. **可视性状态统一**
- 删除 `event_target` 中的 `visibility_` 成员
- 统一使用 `widget_base` 的可视性管理(通过外部状态存储)
- 确保 `get_visibility()` 和 `set_visibility()` 没有二义性
2. **`get_parent_target()` 默认实现**
- 默认直接返回 `parent_` 指针
- 这自动解决了 overlay 等控件的事件冒泡问题
- 某些特殊控件可能需要重写(如跳过某些中间层)
3. **`get_event_children()` 返回类型**
- 从 `std::vector<event_target*>` 改为 `std::vector<widget_base*>`
- 确保所有重写都更新返回类型
4. **scroll_box 的特殊处理**
- 保持其 `on_mouse_scroll()` 处理逻辑不变
- 确保滚动条子控件正确加入 `get_event_children()`
5. **头文件依赖**
- `widget_base.h` 需要包含事件类型定义
- 可能需要前向声明来避免循环依赖
### 5.3 测试要点
1. **事件冒泡测试**
- 在 scroll_box 内的 overlay 上滚动鼠标滚轮
- 验证滚轮事件正确冒泡到 scroll_box
- 验证 scroll_box 正确响应并滚动
2. **焦点导航测试**
- Tab 键导航正常工作
- 鼠标点击正确设置焦点
3. **命中测试**
- 嵌套控件的命中测试正确
- 可视性设置影响命中测试
## 6. 重构前后对比
### 6.1 overlay 类对比
**重构前**
```cpp
class overlay : public container_widget_base, public event_target {
public:
// 缺少 get_parent_target() 重写,导致冒泡链断裂
[[nodiscard]] bool is_event_enabled() const override { return true; }
[[nodiscard]] auto as_event_target() -> event_target* override { return this; }
// ... 重复的 contains_point, global_to_local 等实现
};
```
**重构后**
```cpp
class overlay : public container_widget_base {
public:
// 继承 widget_base 的默认实现
// get_parent_target() 自动返回 parent_冒泡链完整
// contains_point, global_to_local 使用基类默认实现
// 只需重写真正需要自定义的方法
};
```
### 6.2 scroll_box 类对比
**重构前**
```cpp
class scroll_box : public container_widget_base, public event_target {
public:
// 解决多重继承二义性
using widget_base::set_visibility;
using widget_base::get_visibility;
[[nodiscard]] event_target* get_parent_target() const override {
return get_parent_event_target(); // 需要手动转发
}
// ... 重复大量样板代码
};
```
**重构后**
```cpp
class scroll_box : public container_widget_base {
public:
// 不再需要 using 声明解决二义性
// get_parent_target() 使用默认实现,返回 parent_
event_result on_mouse_scroll(const mouse_event& event) override {
// 只保留真正的业务逻辑
}
};
```
## 7. 时间估计
| 阶段 | 预计时间 | 说明 |
|------|----------|------|
| 第一阶段:准备工作 | 2-3 小时 | 添加接口,保持兼容 |
| 第二阶段:更新事件系统 | 3-4 小时 | 修改路由器和焦点管理 |
| 第三阶段:更新控件类 | 2-3 小时 | 删除多重继承 |
| 第四阶段:清理 | 1-2 小时 | 删除废弃代码 |
| 测试与调试 | 2-3 小时 | 全面回归测试 |
| **总计** | **10-15 小时** | |
## 8. 总结
本重构方案通过将 `event_target` 接口合并到 `widget_base` 中,解决了以下问题:
1. **事件冒泡链断裂**:通过 `get_parent_target()` 默认返回 `parent_`,所有控件自动获得正确的事件冒泡行为
2. **多重继承复杂性**:消除了双继承,简化了类层次结构
3. **可视性状态重复**:统一使用 `widget_base` 的状态管理
4. **接口分散**:所有事件相关功能集中在 `widget_base` 中
重构后的架构更加简洁、统一,降低了出错可能性,同时保持了良好的扩展性。
### 8.1 关键收益
- **自动事件冒泡**:控件不再需要手动实现 `get_parent_target()`
- **代码简化**:每个控件减少约 20-30 行样板代码
- **类型安全**:统一使用 `widget_base*` 避免类型转换
- **维护性提升**:单一继承链更易理解和维护
### 8.2 后续工作
完成本重构后,可以考虑:
1. 添加事件过滤器机制
2. 实现事件捕获阶段(从根向下)
3. 优化命中测试性能(空间索引)
4. 添加手势识别支持

View File

@@ -1,360 +0,0 @@
# IME (输入法编辑器) 跨平台实现文档
## 概述
本文档描述了Mirage引擎的跨平台IMEInput Method Editor输入法编辑器系统的完整实现。该系统支持中文、日文、韩文等复杂输入法提供预编辑preedit和文本提交commit功能。
## 架构设计
### 三层架构
1. **平台层** (`src/window/ime_platform.h` 和平台特定实现)
- 提供平台无关的接口
- 封装Windows、Linux、macOS的原生IME API
2. **窗口层** (`src/window/desktop/glfw_window.*`)
- 集成IME到GLFW窗口系统
- 处理IME消息的转发
3. **控件层** (`src/widget/widget_event/ime_manager.*`)
- 管理IME状态和事件
- 提供控件级别的IME接口
- 发布IME事件供控件订阅
## 核心接口
### 平台接口 (`src/window/ime_platform.h`)
```cpp
// 设置IME候选窗口位置
void platform_set_ime_position(void* native_handle, const ime_position_info& pos);
// 启用/禁用IME
void platform_set_ime_enabled(void* native_handle, bool enabled);
// 安装IME消息处理器
void platform_install_ime_handler(void* native_handle,
ime_message_callback callback,
void* user_data);
// 卸载IME消息处理器
void platform_uninstall_ime_handler(void* native_handle);
// 处理平台IME消息
bool platform_process_ime_message(void* native_handle,
unsigned int msg,
uint64_t wparam,
int64_t lparam);
```
### IME消息类型
```cpp
enum class ime_message_type {
preedit_start, ///< 预编辑开始
preedit_update, ///< 预编辑更新
preedit_end, ///< 预编辑结束
commit ///< 提交文本
};
struct ime_message {
ime_message_type type;
std::string_view text; // UTF-8文本
int32_t cursor_pos{0}; // 光标位置
int32_t selection_start{-1}; // 选择起始
int32_t selection_length{0}; // 选择长度
};
```
## 平台特定实现
### Windows实现 (`src/window/desktop/windows/ime_win32.cpp`)
**技术栈**: Windows IMM (Input Method Manager) API
**核心功能**:
- 使用`ImmGetContext`/`ImmReleaseContext`管理IME上下文
- 通过`ImmSetCompositionWindow`设置预编辑窗口位置
- 通过`ImmSetCandidateWindow`设置候选窗口位置
- 处理Windows消息
- `WM_IME_STARTCOMPOSITION` - 预编辑开始
- `WM_IME_COMPOSITION` - 预编辑更新/文本提交
- `WM_IME_ENDCOMPOSITION` - 预编辑结束
**实现要点**:
- 使用窗口属性(`GetPropA`/`SetPropA`)存储IME处理器数据
- 自动处理宽字符到UTF-8的转换
- 支持保存和恢复IME上下文状态
### Linux实现 (`src/window/desktop/linux/ime_x11.cpp`)
**技术栈**: X11 XIM (X Input Method)
**核心功能**:
- 使用`XOpenIM`打开输入法
- 使用`XCreateIC`创建输入上下文
- 通过`XNSpotLocation`设置预编辑位置
- 使用回调机制处理IME事件
- `XNPreeditStartCallback` - 预编辑开始
- `XNPreeditDrawCallback` - 预编辑文本变化
- `XNPreeditDoneCallback` - 预编辑结束
**实现要点**:
- 使用`XIMPreeditCallbacks`风格,应用程序接管预编辑显示
- 支持ibus、fcitx等多种Linux输入法框架
- 通过`XSetICFocus`/`XUnsetICFocus`控制焦点
### macOS实现 (`src/window/desktop/macos/ime_cocoa.mm`)
**技术栈**: Cocoa NSTextInputClient协议
**核心功能**:
- 实现自定义`MirageIMEView`类,遵循`NSTextInputClient`协议
- 实现关键方法:
- `setMarkedText:selectedRange:replacementRange:` - 处理预编辑
- `insertText:replacementRange:` - 处理文本提交
- `firstRectForCharacterRange:actualRange:` - 提供候选窗口位置
- `unmarkText` - 取消预编辑
**实现要点**:
- 使用`NSMutableAttributedString`管理预编辑文本
- 通过`NSTextInputContext`与系统输入法交互
- 自动处理坐标系转换macOS使用左下角原点
## IME管理器 (`src/widget/widget_event/ime_manager.*`)
### 状态管理
```cpp
enum class ime_state : uint8_t {
disabled, ///< IME禁用
enabled, ///< IME启用但未激活
composing ///< IME正在预编辑
};
```
### 核心功能
1. **状态控制**
- `enable()` / `disable()` - 启用/禁用IME
- `is_enabled()` / `is_composing()` - 查询状态
2. **候选窗口定位**
- `set_candidate_position(x, y, line_height)` - 设置位置
- 自动更新平台层位置
3. **预编辑管理**
- `get_preedit()` - 获取当前预编辑信息
- `cancel_composition()` - 取消预编辑
4. **事件发布**
- `on_preedit_start()` - 预编辑开始事件
- `on_preedit_update()` - 预编辑更新事件
- `on_preedit_end()` - 预编辑结束事件
- `on_commit()` - 文本提交事件
### 焦点联动
```cpp
void ime_manager::set_focused_target(widget_base* target);
```
当控件获得焦点时自动根据控件的IME支持能力启用/禁用IME。
## 使用示例
### 在文本输入控件中使用IME
```cpp
class text_input : public leaf_widget_base {
public:
void on_focus_gained() override {
// 启用IME
if (auto* ctx = get_context()) {
auto& ime = ctx->get_ime_manager();
ime.enable();
ime.set_focused_target(this);
update_ime_position();
}
}
void on_focus_lost() override {
// 禁用IME
if (auto* ctx = get_context()) {
auto& ime = ctx->get_ime_manager();
ime.disable();
}
}
void setup_ime_events() {
auto& ime = get_context()->get_ime_manager();
// 订阅预编辑更新
ime.on_preedit_update().subscribe([this](const ime_preedit_event& e) {
preedit_text_ = e.preedit.text;
preedit_cursor_ = e.preedit.cursor_pos;
mark_render_dirty();
});
// 订阅文本提交
ime.on_commit().subscribe([this](const ime_commit_event& e) {
insert_text(e.text);
preedit_text_.clear();
mark_render_dirty();
});
}
void update_ime_position() {
auto cursor_pos = calculate_cursor_screen_position();
auto& ime = get_context()->get_ime_manager();
ime.set_candidate_position(
static_cast<int32_t>(cursor_pos.x()),
static_cast<int32_t>(cursor_pos.y()),
static_cast<int32_t>(font_size_)
);
}
private:
std::string preedit_text_;
int32_t preedit_cursor_{0};
};
```
### 渲染预编辑文本
```cpp
void text_input::render(render_command_collector& collector) {
// 1. 渲染普通文本
collector.submit_text(pos, text_, color_, font_size_, font_id_);
// 2. 渲染预编辑文本(带下划线)
if (!preedit_text_.empty()) {
auto preedit_pos = calculate_preedit_position();
// 渲染预编辑文本
collector.submit_text(preedit_pos, preedit_text_,
preedit_color_, font_size_, font_id_);
// 渲染预编辑下划线
auto underline_y = preedit_pos.y() + font_size_;
collector.submit_line(
{preedit_pos.x(), underline_y},
{preedit_pos.x() + preedit_width, underline_y},
preedit_underline_color_
);
// 渲染预编辑光标
if (preedit_cursor_ >= 0) {
auto cursor_x = calculate_cursor_x_in_preedit(preedit_cursor_);
collector.submit_rect(
{cursor_x, preedit_pos.y()},
{cursor_width_, font_size_},
cursor_color_
);
}
}
}
```
## 事件流程
### 输入中文"你好"的完整流程
1. **用户开始输入**
- 系统发送 `preedit_start`
- `ime_manager` 状态: `disabled``composing`
2. **用户输入拼音 "nihao"**
- 系统发送 `preedit_update("nihao", cursor=5)`
- 控件显示下划线文本: "nihao"
3. **用户选择候选词**
- 系统发送 `preedit_update("你好", cursor=2)`
- 控件更新显示: "你好"
4. **用户按空格确认**
- 系统发送 `commit("你好")`
- 控件插入文本: "你好"
- 系统发送 `preedit_end`
- `ime_manager` 状态: `composing``enabled`
## 测试验证
### 测试要点
1. **基础功能测试**
- [ ] 启用/禁用IME
- [ ] 预编辑文本显示
- [ ] 候选窗口位置正确
- [ ] 文本正确提交
2. **跨平台测试**
- [ ] Windows (中文/日文输入法)
- [ ] Linux (ibus/fcitx)
- [ ] macOS (内置输入法)
3. **边界情况测试**
- [ ] 焦点切换时IME状态
- [ ] 预编辑中途取消
- [ ] 窗口移动时候选窗口位置
- [ ] 多窗口IME状态独立性
## 注意事项
### Windows
- IME上下文必须正确保存和恢复
- 需要链接 `imm32.lib`
- 坐标系统:左上角为原点
### Linux
- 需要X11开发库`libx11-dev`
- 支持多种输入法框架ibus、fcitx等
- 坐标系统:左上角为原点
- Display指针管理需要注意
### macOS
- 文件扩展名必须是`.mm`Objective-C++
- 需要实现完整的NSTextInputClient协议
- 坐标系统左下角为原点需要Y坐标翻转
- 视图替换需要保留原有子视图
## 性能考虑
1. **内存管理**
- 预编辑文本使用`std::string`,避免频繁分配
- 平台数据使用智能指针管理生命周期
2. **事件优化**
- 使用pub-sub模式避免轮询
- 事件只发送给订阅的控件
3. **线程安全**
- IME操作必须在主线程执行
- 使用`threading::topic`保证事件分发的线程安全
## 未来改进
1. **功能增强**
- [ ] 支持内联候选窗口(候选词显示在应用内)
- [ ] 支持更多IME属性字体、颜色等
- [ ] 支持Windows TSF (Text Services Framework)
2. **平台扩展**
- [ ] Wayland支持Linux
- [ ] Android/iOS移动平台支持
3. **API改进**
- [ ] 提供更细粒度的控制接口
- [ ] 支持自定义候选窗口样式
## 相关文档
- [键盘事件系统设计](KEYBOARD_EVENT_SYSTEM.md)
- [文本输入控件设计](TEXT_INPUT_DESIGN.md)
- [文本输入架构分析](TEXT_INPUT_ARCHITECTURE_ANALYSIS.md)
## 参考资料
- [Windows IMM API文档](https://docs.microsoft.com/en-us/windows/win32/intl/input-method-manager)
- [X11 XIM规范](https://www.x.org/releases/X11R7.7/doc/libX11/XIM/xim.html)
- [macOS NSTextInputClient文档](https://developer.apple.com/documentation/appkit/nstextinputclient)

File diff suppressed because it is too large Load Diff

View File

@@ -1,403 +0,0 @@
# MTSDF纹理多线程锁优化方案
## 概述
本文档描述了对`async_mtsdf_generator``glyph_cache`的多线程锁优化方案目标是使用C++23标准特性提升响应速度减少锁竞争。
## 问题分析
### 1. async_mtsdf_generator 当前问题
**问题1: mpsc_queue误用导致的锁竞争**
```cpp
// 当前实现 (async_mtsdf_generator.cpp:156-174)
void worker_loop(worker_context* worker, std::stop_token stop_token) {
while (!stop_token.stop_requested()) {
std::optional<internal_task> task_opt;
{
// ❌ 所有worker线程竞争同一把锁
std::unique_lock lock(task_queue_mutex);
// ❌ condition_variable开销大可能产生虚假唤醒
task_available_cv.wait(lock, [this, &stop_token] {
return stop_token.stop_requested() || !task_queue.empty();
});
task_opt = task_queue.try_pop();
}
// ...
}
}
```
**问题分析**:
- `mpsc_queue` 设计为多生产者单消费者但这里有N个worker同时消费
- 必须用mutex保护消费操作完全抵消了无锁队列的优势
- N个worker线程竞争同一把mutex造成严重的锁竞争
- `condition_variable::wait()` 需要持有锁,唤醒后需要重新获取锁
**问题2: 取消任务的锁开销**
```cpp
// 当前实现 (async_mtsdf_generator.cpp:138-153)
auto is_cancelled(uint64_t task_id) -> bool {
std::lock_guard lock(cancel_mutex); // ❌ 每次检查都加锁
return cancelled_tasks.contains(task_id);
}
```
### 2. glyph_cache 当前问题
**问题: O(n)遍历查找pending任务**
```cpp
// 当前实现 (glyph_cache.cpp:191-204)
{
std::shared_lock lock(task_mutex_);
// ❌ O(n)遍历所有pending任务
for (const auto& [task_id, pending] : task_to_key_) {
if (pending.user_key == user_key) {
return glyph_lookup_result{...};
}
}
}
```
---
## 优化方案
### 方案1: 每Worker独立队列 + Round-Robin分发
#### 架构设计
```
提交任务 ──► Round-Robin分发器 ──┬──► Worker 0 [spsc_queue] ──► atomic wait
├──► Worker 1 [spsc_queue] ──► atomic wait
└──► Worker N [spsc_queue] ──► atomic wait
```
#### 数据结构
```cpp
struct worker_context {
uint32_t worker_id;
std::jthread thread;
FT_Library ft_library{nullptr};
std::unordered_map<std::string, FT_Face> font_cache;
// 每个worker独立的无锁队列
spsc_queue<internal_task, 256> task_queue;
// C++20 atomic wait/notify 替代 condition_variable
std::atomic<uint32_t> pending_count{0};
// ... 构造函数和方法 ...
};
```
#### Worker循环优化
```cpp
void worker_loop(worker_context* worker, std::stop_token stop_token) {
while (!stop_token.stop_requested()) {
// 尝试从本地队列获取任务(无锁)
auto task_opt = worker->task_queue.try_pop();
if (!task_opt.has_value()) {
// 无任务使用atomic wait等待
uint32_t current = worker->pending_count.load(std::memory_order_acquire);
if (current == 0) {
// C++20: 无锁等待比condition_variable更轻量
worker->pending_count.wait(0, std::memory_order_relaxed);
}
continue;
}
// 减少pending计数
worker->pending_count.fetch_sub(1, std::memory_order_relaxed);
// 执行任务...
}
}
```
#### 任务提交优化
```cpp
auto async_mtsdf_generator::submit(glyph_generation_request request) -> glyph_task_id {
const uint64_t id = next_task_id_.fetch_add(1, std::memory_order_relaxed);
internal_task task{
.task_id = glyph_task_id{id},
.request = std::move(request)
};
// Round-Robin选择worker无锁
const std::size_t worker_idx = next_worker_index_.fetch_add(1, std::memory_order_relaxed)
% workers_.size();
auto& worker = workers_[worker_idx];
// 推送到worker的spsc队列无锁单生产者
while (!worker->task_queue.try_push(std::move(task))) {
// 队列满尝试下一个worker可选
std::this_thread::yield();
}
// 增加pending计数并唤醒worker
worker->pending_count.fetch_add(1, std::memory_order_release);
worker->pending_count.notify_one(); // C++20: 精确唤醒单个worker
pending_tasks_.fetch_add(1, std::memory_order_relaxed);
return glyph_task_id{id};
}
```
### 方案2: 取消机制优化
#### 使用per-task atomic_flag
```cpp
struct internal_task {
glyph_task_id task_id;
glyph_generation_request request;
std::shared_ptr<std::atomic_flag> cancelled; // 每任务独立的取消标志
};
// 取消检查 - 无锁
auto is_cancelled(const internal_task& task) -> bool {
return task.cancelled && task.cancelled->test(std::memory_order_relaxed);
}
// 取消操作 - 无锁
auto cancel(glyph_task_id id) -> bool {
std::shared_lock lock(task_registry_mutex_);
auto it = task_registry_.find(id);
if (it == task_registry_.end()) return false;
it->second->test_and_set(std::memory_order_relaxed);
return true;
}
```
### 方案3: glyph_cache pending查找优化
#### 添加反向索引
```cpp
class glyph_cache {
private:
// 原有task_id -> pending_task
std::unordered_map<glyph_task_id, pending_task, task_id_hash> task_to_key_;
// 新增user_key -> task_id (反向索引)
std::unordered_map<atlas_user_key, glyph_task_id> key_to_task_;
mutable std::shared_mutex task_mutex_; // 保护两个map
};
// 优化后的查找 - O(1)
auto find_pending_task(atlas_user_key user_key) -> std::optional<glyph_task_id> {
std::shared_lock lock(task_mutex_);
auto it = key_to_task_.find(user_key);
return (it != key_to_task_.end()) ? std::optional{it->second} : std::nullopt;
}
```
---
## 详细实现计划
### 阶段1: async_mtsdf_generator 重构
#### 1.1 新增数据结构
```cpp
// 新的impl结构
struct async_mtsdf_generator::impl {
// Worker上下文每个worker独立队列
struct worker_context {
uint32_t worker_id;
std::jthread thread;
FT_Library ft_library{nullptr};
std::unordered_map<std::string, FT_Face> font_cache;
// 独立的spsc队列
spsc_queue<internal_task, 256> task_queue;
// atomic wait/notify
std::atomic<uint32_t> pending_count{0};
// 构造/析构...
};
std::vector<std::unique_ptr<worker_context>> workers_;
// 结果队列保持mpsc因为多个worker生产单消费者收集
mpsc_queue<glyph_generation_result> result_queue_;
// 任务计数
std::atomic<uint64_t> next_task_id_{1};
std::atomic<std::size_t> pending_tasks_{0};
std::atomic<std::size_t> next_worker_index_{0};
// 任务注册表(用于取消)
std::shared_mutex task_registry_mutex_;
std::unordered_map<uint64_t, std::shared_ptr<std::atomic_flag>> task_registry_;
};
```
#### 1.2 删除的代码
```cpp
// 删除以下成员
std::mutex task_queue_mutex; // 不再需要
std::condition_variable task_available_cv; // 用atomic wait替代
std::mutex cancel_mutex; // 用per-task atomic替代
std::unordered_set<uint64_t> cancelled_tasks; // 用atomic_flag替代
```
### 阶段2: glyph_cache 优化
#### 2.1 添加反向索引
```cpp
// glyph_cache.h 新增成员
private:
std::unordered_map<atlas_user_key, glyph_task_id> key_to_task_;
```
#### 2.2 修改任务提交/完成逻辑
```cpp
// 提交时同时更新两个map
void register_pending_task(glyph_task_id task_id, atlas_user_key user_key) {
std::unique_lock lock(task_mutex_);
task_to_key_[task_id] = pending_task{.user_key = user_key, ...};
key_to_task_[user_key] = task_id;
}
// 完成时同时删除两个map
void unregister_pending_task(glyph_task_id task_id) {
std::unique_lock lock(task_mutex_);
auto it = task_to_key_.find(task_id);
if (it != task_to_key_.end()) {
key_to_task_.erase(it->second.user_key);
task_to_key_.erase(it);
}
}
```
---
## 性能对比预估
| 操作 | 当前延迟 | 优化后延迟 | 改善 |
|------|----------|------------|------|
| 任务提交 | ~1-5μs (mutex竞争) | ~50-100ns (无锁) | 10-50x |
| Worker等待 | ~10-50μs (CV唤醒) | ~1-5μs (atomic wait) | 10x |
| 取消检查 | ~100-500ns (mutex) | ~10-20ns (atomic) | 10-25x |
| pending查找 | O(n) ~1-10μs | O(1) ~50ns | 20-200x |
---
## 线程安全分析
### 生产者-消费者关系
```
┌─────────────────────────────────────┐
│ async_mtsdf_generator │
│ │
submit() ───────►│ Round-Robin ──► Worker 0 spsc │──► result_queue (mpsc)
(主线程) │ ──► Worker 1 spsc │ │
│ ──► Worker N spsc │ ▼
└─────────────────────────────────────┘ poll_completed()
(主线程)
```
### 队列选择依据
| 队列 | 生产者 | 消费者 | 类型 |
|------|--------|--------|------|
| Worker task_queue | 单(submit线程) | 单(worker线程) | spsc_queue |
| result_queue | 多(N个worker) | 单(poll_completed) | mpsc_queue |
---
## 文件修改清单
### 需要修改的文件
1. **`src/render/text/async_mtsdf_generator.h`**
- 保持公共接口不变
- 内部impl结构重构
2. **`src/render/text/async_mtsdf_generator.cpp`**
- 重写worker_loop使用atomic wait
- 重写submit使用Round-Robin + spsc队列
- 重写cancel使用per-task atomic_flag
3. **`src/render/text/glyph_cache.h`**
- 添加`key_to_task_`反向索引成员
4. **`src/render/text/glyph_cache.cpp`**
- 优化`get_or_request_glyph`中的pending查找
- 更新任务注册/注销逻辑
### 不需要修改的文件
- `src/common/threading/message_queue.h` - spsc_queue/mpsc_queue实现已完备
- `src/render/text/mtsdf_generator.h/cpp` - MTSDF生成逻辑不变
- 公共API保持完全向后兼容
---
## 测试计划
### 单元测试
1. **spsc队列正确性测试**
- 单生产者单消费者场景
- 边界条件:队列满、队列空
2. **atomic wait/notify测试**
- 基本等待/唤醒
- 多worker并发场景
3. **取消机制测试**
- 取消未开始的任务
- 取消正在执行的任务
### 性能测试
1. **吞吐量测试**
- 提交10000个任务测量总完成时间
- 对比优化前后
2. **延迟测试**
- 测量单个任务从提交到开始执行的延迟
- 测量结果收集延迟
3. **竞争测试**
- 多线程并发提交
- 压力测试下的稳定性
---
## 风险评估
| 风险 | 可能性 | 影响 | 缓解措施 |
|------|--------|------|----------|
| spsc队列满导致任务丢失 | 低 | 高 | 队列满时spin等待或扩容 |
| atomic wait平台兼容性 | 低 | 中 | C++20标准主流编译器支持 |
| 性能回退 | 低 | 中 | 保留旧实现作为fallback |
---
## 参考资料
- [C++20 atomic wait/notify](https://en.cppreference.com/w/cpp/atomic/atomic/wait)
- [Lock-free programming patterns](https://www.cs.cmu.edu/~410-f17/lectures/L31_LockFree.pdf)
- [SPSC Queue implementation](https://rigtorp.se/ringbuffer/)

File diff suppressed because it is too large Load Diff

View File

@@ -1,642 +0,0 @@
# 后效管线增量渲染架构设计
## 1. 架构概述
### 1.1 设计目标
本文档描述了一种后效管线优化方案,使区域后效能够与增量渲染机制共存。核心目标是:
1. **减少不必要的后效计算**:当脏区域与后效区域不重叠时,跳过该后效的重新计算
2. **精确化渲染范围**:只为实际需要的区域分配临时缓冲并执行后效计算
3. **智能脏区域扩展**:考虑后效的影响半径(如模糊采样范围)自动扩展脏区域
### 1.2 重要约束
**所有后效都是区域后效widget 级别)**,除非该 widget 的大小等于整个视口。这意味着:
- 每个后效都有明确的边界信息(来自 `post_effect_command``position``size`
- 不需要考虑全屏后效的特殊情况
- 后效天然具有区域属性
### 1.3 系统架构图
```mermaid
flowchart TB
subgraph Input
DirtyRect[脏区域 dirty_rect]
RenderTree[渲染树 render_tree]
end
subgraph Analysis[后效分析阶段]
Collector[PostEffectRegionCollector]
Analyzer[DirtyRegionAnalyzer]
Collector --> |收集所有后效区域| EffectRegions[后效区域列表]
DirtyRect --> Analyzer
EffectRegions --> Analyzer
Analyzer --> |计算影响半径扩展| ExpandedDirty[扩展后脏区域]
Analyzer --> |判断重叠关系| EffectDecisions[后效决策表]
end
subgraph Execution[执行阶段]
EffectDecisions --> Executor[IncrementalEffectExecutor]
ExpandedDirty --> Executor
Executor --> |根据决策| Skip[跳过无关后效]
Executor --> |根据决策| PartialRender[部分区域渲染]
Executor --> |根据决策| FullRender[完整区域渲染]
end
subgraph Cache[缓存系统]
EffectCache[后效结果缓存]
Skip --> |使用缓存| EffectCache
PartialRender --> |更新缓存| EffectCache
FullRender --> |更新缓存| EffectCache
end
subgraph Output
FinalResult[最终渲染结果]
end
EffectCache --> FinalResult
```
## 2. 核心算法
### 2.1 后效区域与脏区域交互判断算法
```mermaid
flowchart TD
Start[开始处理后效节点] --> GetBounds[获取后效边界 effect_bounds]
GetBounds --> GetRadius[计算影响半径 influence_radius]
GetRadius --> ExpandEffect[扩展后效区域 expanded_effect_bounds]
ExpandEffect --> CheckIntersect{扩展后效区域 与 脏区域 是否相交?}
CheckIntersect --> |否| SkipEffect[跳过后效计算 使用缓存结果]
CheckIntersect --> |是| CalcOverlap[计算精确重叠区域]
CalcOverlap --> CheckRatio{重叠面积 / 后效面积 > 阈值?}
CheckRatio --> |是 大于80%| FullRecompute[完整重新计算后效]
CheckRatio --> |否| PartialRecompute[部分区域重新计算]
FullRecompute --> AllocFull[分配完整尺寸缓冲]
PartialRecompute --> AllocPartial[分配部分尺寸缓冲]
AllocFull --> Execute[执行后效]
AllocPartial --> Execute
Execute --> UpdateCache[更新缓存]
SkipEffect --> End[结束]
UpdateCache --> End
```
### 2.2 影响半径计算
不同类型的后效有不同的影响半径:
| 后效类型 | 影响半径计算方式 |
|---------|-----------------|
| `blur_effect` | `radius * 2`(双向采样) |
| `chromatic_aberration_effect` | `max(abs(offset.x), abs(offset.y))` |
| `vignette_effect` | `0`(无依赖) |
| `color_adjust_effect` | `0`(无依赖) |
| `color_tint_effect` | `0`(无依赖) |
| `noise_effect` | `0`(无依赖) |
### 2.3 智能脏区域扩展算法
```cpp
/// 计算后效对脏区域的扩展需求
/// @param dirty_rect 原始脏区域
/// @param effect_regions 所有后效区域及其影响半径
/// @return 扩展后的脏区域
auto compute_expanded_dirty_region(
const aabb2d_t& dirty_rect,
const std::vector<effect_region_info>& effect_regions
) -> aabb2d_t {
aabb2d_t expanded = dirty_rect;
for (const auto& effect_info : effect_regions) {
// 检查原始脏区域是否与后效区域相交
if (!dirty_rect.intersects(effect_info.bounds)) {
continue;
}
// 扩展脏区域以包含该后效的影响范围
float radius = effect_info.influence_radius;
if (radius > 0) {
// 计算需要扩展的区域(脏区域与后效区域的交集 + 影响半径)
auto intersection = dirty_rect.intersection(effect_info.bounds);
aabb2d_t effect_expanded(
intersection.min() - vec2f_t(radius, radius),
intersection.max() + vec2f_t(radius, radius)
);
// 但扩展不能超出后效区域本身
effect_expanded = effect_expanded.intersection(effect_info.bounds);
expanded = expanded.merged(effect_expanded);
}
}
return expanded;
}
```
## 3. 数据结构定义
### 3.1 后效区域信息
```cpp
namespace mirage::render {
/// @brief 后效影响半径计算器
struct effect_influence_calculator {
/// 计算后效的影响半径(像素)
static auto compute_radius(const post_effect_command& effect) -> float {
return std::visit([](const auto& eff) -> float {
using T = std::decay_t<decltype(eff)>;
if constexpr (std::is_same_v<T, blur_effect>) {
// 模糊需要读取周围像素,半径 = blur_radius * 2
return eff.radius * 2.0f;
}
else if constexpr (std::is_same_v<T, chromatic_aberration_effect>) {
// 色差需要偏移采样
return std::max(std::abs(eff.offset.x()), std::abs(eff.offset.y()));
}
else {
// 其他效果不需要额外像素
return 0.0f;
}
}, effect);
}
};
/// @brief 后效区域信息
struct effect_region_info {
aabb2d_t bounds; ///< 后效边界
float influence_radius; ///< 影响半径(像素)
post_effect_command effect; ///< 后效命令
const post_effect_node* node; ///< 关联的渲染树节点
/// 获取扩展后的边界(包含影响半径)
[[nodiscard]] auto expanded_bounds() const -> aabb2d_t {
if (influence_radius <= 0) {
return bounds;
}
return aabb2d_t(
bounds.min() - vec2f_t(influence_radius, influence_radius),
bounds.max() + vec2f_t(influence_radius, influence_radius)
);
}
};
/// @brief 后效渲染决策
enum class effect_render_decision {
skip, ///< 完全跳过,使用缓存
partial, ///< 部分重新计算
full ///< 完整重新计算
};
/// @brief 后效决策结果
struct effect_decision_result {
effect_render_decision decision; ///< 决策类型
aabb2d_t render_region; ///< 需要渲染的区域
float overlap_ratio; ///< 重叠比例(用于调试)
};
/// @brief 脏区域分析器
class dirty_region_analyzer {
public:
/// 配置参数
struct config {
float full_render_threshold = 0.8f; ///< 超过此比例则完整渲染
float min_partial_size = 64.0f; ///< 最小部分渲染尺寸
bool enable_caching = true; ///< 是否启用缓存
};
explicit dirty_region_analyzer(const config& cfg = {});
/// 分析后效节点与脏区域的关系
/// @param effect_info 后效区域信息
/// @param dirty_rect 脏区域
/// @param viewport_size 视口大小
/// @return 渲染决策
[[nodiscard]] auto analyze(
const effect_region_info& effect_info,
const aabb2d_t& dirty_rect,
const vec2f_t& viewport_size
) const -> effect_decision_result;
/// 批量分析所有后效
[[nodiscard]] auto analyze_all(
const std::vector<effect_region_info>& effects,
const aabb2d_t& dirty_rect,
const vec2f_t& viewport_size
) const -> std::vector<effect_decision_result>;
private:
config config_;
};
} // namespace mirage::render
```
### 3.2 后效结果缓存
```cpp
namespace mirage::render {
/// @brief 后效缓存条目
struct effect_cache_entry {
/// 缓存键(用于匹配)
struct key {
aabb2d_t bounds; ///< 后效边界
size_t effect_hash; ///< 后效参数哈希
bool operator==(const key& other) const;
};
target_handle cached_result; ///< 缓存的渲染结果
key cache_key; ///< 缓存键
uint64_t frame_created; ///< 创建帧
uint64_t last_used; ///< 最后使用帧
bool valid; ///< 是否有效
};
/// @brief 后效结果缓存管理器
class effect_cache_manager {
public:
struct config {
size_t max_entries = 32; ///< 最大缓存条目数
uint32_t max_age_frames = 60; ///< 最大缓存帧数
size_t max_memory_mb = 256; ///< 最大内存使用MB
};
effect_cache_manager(unified_target_pool& pool, const config& cfg = {});
/// 查找缓存
/// @return 缓存的渲染目标,或 nullptr
[[nodiscard]] auto find(const effect_cache_entry::key& key)
-> offscreen_target*;
/// 存储缓存
void store(const effect_cache_entry::key& key, target_handle result);
/// 使缓存条目失效
void invalidate(const aabb2d_t& region);
/// 帧结束时清理过期缓存
void end_frame(uint64_t current_frame);
/// 清除所有缓存
void clear();
private:
unified_target_pool& pool_;
config config_;
std::vector<effect_cache_entry> entries_;
uint64_t current_frame_ = 0;
};
} // namespace mirage::render
```
### 3.3 增量后效执行器
```cpp
namespace mirage::render {
/// @brief 增量后效执行上下文
template <typename State>
struct incremental_effect_context {
executor_context<State>* base_ctx; ///< 基础执行上下文
dirty_region_analyzer* analyzer; ///< 脏区域分析器
effect_cache_manager* cache; ///< 缓存管理器
std::optional<aabb2d_t> dirty_rect; ///< 当前脏区域
vec2f_t viewport_size; ///< 视口大小
std::vector<effect_decision_result> decisions; ///< 预计算的决策
};
/// @brief 增量后效执行器
class incremental_effect_executor {
public:
struct config {
bool enable_partial_render = true; ///< 启用部分渲染
bool enable_caching = true; ///< 启用缓存
float partial_threshold = 0.8f; ///< 部分渲染阈值
};
incremental_effect_executor(
post_effect_applicator& applicator,
unified_target_pool& pool,
const config& cfg = {}
);
/// 预分析渲染树中的所有后效
[[nodiscard]] auto analyze_effects(
const render_tree& tree,
const std::optional<aabb2d_t>& dirty_rect,
const vec2f_t& viewport_size
) -> std::vector<effect_decision_result>;
/// 执行后效节点(带增量渲染支持)
template <typename State>
void execute_effect(
const post_effect_node& node,
incremental_effect_context<State>& ctx,
size_t effect_index
);
private:
post_effect_applicator& applicator_;
unified_target_pool& pool_;
config config_;
dirty_region_analyzer analyzer_;
effect_cache_manager cache_;
};
} // namespace mirage::render
```
## 4. 处理流程
### 4.1 完整流程图
```mermaid
sequenceDiagram
participant RP as RenderPipeline
participant Analyzer as DirtyRegionAnalyzer
participant Executor as RenderTreeExecutor
participant Cache as EffectCacheManager
participant Pool as UnifiedTargetPool
participant Applicator as PostEffectApplicator
Note over RP: render_frame 开始
RP->>Analyzer: 收集渲染树中的后效区域
Analyzer->>Analyzer: 计算每个后效的影响半径
Analyzer->>Analyzer: 扩展脏区域
Analyzer-->>RP: 扩展后的脏区域 + 后效决策表
RP->>Executor: 执行渲染树
loop 对于每个后效节点
Executor->>Executor: 获取该节点的决策
alt 决策 = skip
Executor->>Cache: 查找缓存
Cache-->>Executor: 返回缓存结果
Executor->>Executor: Blit 缓存到目标
else 决策 = partial
Executor->>Pool: 获取部分尺寸缓冲
Pool-->>Executor: 返回小尺寸目标
Executor->>Applicator: 执行部分区域后效
Applicator-->>Executor: 完成
Executor->>Cache: 更新缓存(合并)
else 决策 = full
Executor->>Pool: 获取完整尺寸缓冲
Pool-->>Executor: 返回完整目标
Executor->>Applicator: 执行完整后效
Applicator-->>Executor: 完成
Executor->>Cache: 更新缓存
end
end
RP->>Cache: 帧结束清理
Note over RP: render_frame 结束
```
### 4.2 后效执行详细流程
```mermaid
flowchart TD
subgraph PreAnalysis[预分析阶段]
CollectEffects[遍历渲染树收集后效]
CalcInfluence[计算影响半径]
ExpandDirty[扩展脏区域]
MakeDecisions[生成决策表]
CollectEffects --> CalcInfluence
CalcInfluence --> ExpandDirty
ExpandDirty --> MakeDecisions
end
subgraph Execution[执行阶段 - 每个后效]
GetDecision[获取决策]
GetDecision --> CheckSkip{skip?}
CheckSkip -->|是| LoadCache[加载缓存]
CheckSkip -->|否| CheckPartial{partial?}
CheckPartial -->|是| AllocSmall[分配小缓冲]
CheckPartial -->|否| AllocFull[分配完整缓冲]
AllocSmall --> RenderPartial[渲染部分区域]
AllocFull --> RenderFull[渲染完整区域]
LoadCache --> BlitCache[Blit 缓存到目标]
RenderPartial --> MergeCache[合并到缓存]
RenderFull --> ReplaceCache[替换缓存]
BlitCache --> Done[完成]
MergeCache --> Done
ReplaceCache --> Done
end
PreAnalysis --> Execution
```
## 5. 实现步骤
按优先级排序的实现步骤:
### Phase 1: 基础设施(优先级:高)
1. **创建 `effect_influence_calculator`**
- 文件:`src/render/pipeline/effect_influence.h`
- 实现各类后效的影响半径计算
- 估计工时2小时
2. **创建 `dirty_region_analyzer`**
- 文件:`src/render/pipeline/dirty_region_analyzer.h/.cpp`
- 实现脏区域与后效区域的交互判断
- 估计工时4小时
3. **创建 `effect_region_info` 和相关数据结构**
- 文件:`src/render/pipeline/effect_types.h`
- 定义后效区域信息结构
- 估计工时1小时
### Phase 2: 缓存系统(优先级:中)
4. **创建 `effect_cache_manager`**
- 文件:`src/render/pipeline/effect_cache_manager.h/.cpp`
- 实现后效结果缓存
- 估计工时6小时
5. **修改 `unified_target_pool`**
- 添加按区域大小分配支持
- 优化小尺寸目标的复用策略
- 估计工时3小时
### Phase 3: 执行器集成(优先级:高)
6. **创建 `incremental_effect_executor`**
- 文件:`src/render/pipeline/incremental_effect_executor.h/.cpp`
- 整合分析器和缓存
- 估计工时6小时
7. **修改 `render_tree_executor`**
- 集成增量后效执行器
-`execute_post_effect` 中添加决策逻辑
- 估计工时4小时
### Phase 4: 管线集成(优先级:高)
8. **修改 `render_pipeline`**
-`render_frame` 中添加预分析阶段
- 传递决策表到执行器
- 估计工时3小时
9. **修改 `post_effect_applicator`**
- 添加部分区域渲染支持
- 优化 `blit_to_target` 支持区域偏移
- 估计工时4小时
### Phase 5: 测试与优化(优先级:中)
10. **单元测试**
- 测试影响半径计算
- 测试脏区域分析
- 测试缓存命中率
- 估计工时4小时
11. **性能基准测试**
- 对比优化前后的性能
- 验证内存使用改善
- 估计工时2小时
## 6. 性能预期
### 6.1 最佳情况
| 场景 | 优化前 | 优化后 | 改善 |
|-----|-------|-------|------|
| 小区域脏区域,无后效重叠 | 全量后效计算 | 跳过所有后效 | ~100% |
| 小区域脏区域,部分后效重叠 | 全量后效计算 | 仅计算重叠部分 | ~60-80% |
| 大区域脏区域 | 全量后效计算 | 全量后效计算 | 0% |
### 6.2 内存使用
| 资源 | 优化前 | 优化后 |
|-----|-------|-------|
| 临时缓冲 | 全尺寸 | 按需分配(可能更小) |
| 缓存内存 | 无 | 可配置上限默认256MB |
| 目标池峰值 | 高 | 略高(但可控) |
### 6.3 典型场景分析
**场景1鼠标悬停按钮小区域更新**
- 脏区域50x30 像素
- 视口1920x1080
- 后效:全屏模糊遮罩
- 预期:后效区域与脏区域不重叠 → **跳过后效**
**场景2滚动列表中等区域更新**
- 脏区域300x800 像素
- 视口1920x1080
- 后效列表项上的模糊背景200x100
- 预期:部分后效与脏区域重叠 → **部分渲染**
**场景3窗口拖动大区域更新**
- 脏区域1000x800 像素
- 视口1920x1080
- 后效窗口模糊背景800x600
- 预期:大部分重叠 → **完整渲染**
## 7. 与现有系统的集成点
### 7.1 render_tree.h
- `post_effect_node` 已包含 `bounds` 信息
- 无需修改现有结构
### 7.2 render_pipeline.cpp
需要修改的位置:
```cpp
// 在 render_frame 开始处添加预分析
auto effect_decisions = incremental_executor_->analyze_effects(
tree,
dirty_rect,
vec2f_t(extent.width, extent.height)
);
// 在 executor_context 中添加决策信息
render::incremental_effect_context<pass_state::offscreen_pass_tag> inc_ctx{
.base_ctx = &exec_ctx,
.analyzer = &dirty_analyzer_,
.cache = &effect_cache_,
.dirty_rect = dirty_rect,
.viewport_size = vec2f_t(extent.width, extent.height),
.decisions = effect_decisions
};
```
### 7.3 render_tree_executor.cpp
`execute_post_effect` 中添加:
```cpp
// 获取决策
if (ctx.incremental_ctx && ctx.incremental_ctx->dirty_rect.has_value()) {
auto& decisions = ctx.incremental_ctx->decisions;
auto& decision = decisions[effect_index];
switch (decision.decision) {
case effect_render_decision::skip:
// 使用缓存
apply_cached_effect(node, ctx);
return;
case effect_render_decision::partial:
// 部分渲染
apply_partial_effect(node, ctx, decision.render_region);
return;
case effect_render_decision::full:
// 完整渲染fallthrough to existing logic
break;
}
}
// 现有的完整渲染逻辑...
```
### 7.4 post_effect_applicator.cpp
添加新接口:
```cpp
/// 应用后效到指定区域(增量渲染用)
void apply_region(
vk::CommandBuffer cmd,
offscreen_target& target,
const post_effect_command& effect,
float time,
const aabb2d_t& render_region, // 只渲染这个区域
const offscreen_target* source = nullptr
);
```
## 8. 风险与缓解
| 风险 | 影响 | 缓解措施 |
|-----|------|---------|
| 缓存一致性问题 | 渲染错误 | 使用效果参数哈希验证缓存有效性 |
| 边界情况处理 | 视觉瑕疵 | 充分的边界扩展 + 渐变处理 |
| 内存使用增加 | OOM | 可配置的缓存上限 + 自动清理 |
| 实现复杂度 | 维护困难 | 清晰的分层设计 + 充分注释 |
## 9. 未来扩展
1. **多帧时序缓存**:利用帧间相关性进一步优化
2. **GPU 异步计算**:将后效计算移到独立的 compute queue
3. **自适应质量**:根据脏区域大小动态调整后效质量
4. **后效合并**:将相同类型的后效批量处理

File diff suppressed because it is too large Load Diff

View File

@@ -1,660 +0,0 @@
# 文本输入框控件架构分析文档
本文档分析了 mirage 框架的现有架构,为实现文本输入框控件提供技术参考。
## 1. Widget 基类结构分析
### 1.1 类继承层次
```
widget_base (抽象基类)
├── leaf_widget_base (叶子控件基类)
│ ├── text_widget (文本显示)
│ ├── scrollbar (滚动条)
│ ├── fill_box (填充盒)
│ └── imager (图片)
├── container_widget_base (容器控件基类)
│ ├── v_stack, h_stack (栈式布局)
│ ├── overlay (层叠布局)
│ └── scroll_box (滚动容器)
└── composite_widget_base (组合控件基类)
└── (自定义复合控件)
```
### 1.2 widget_base 关键接口
| 接口 | 说明 | 文本输入框需要 |
|------|------|----------------|
| [`do_measure()`](src/widget/widget_base.h:375) | 测量控件期望大小 | ✅ 是 |
| [`arrange()`](src/widget/widget_base.h:111) | 布局控件到指定状态 | ✅ 是 |
| [`build_render_command()`](src/widget/widget_base.h:182) | 构建渲染命令 | ✅ 是 |
| [`on_key_down()`](src/widget/widget_base.h:256) | 键盘按下事件 | ✅ 是 |
| [`on_key_up()`](src/widget/widget_base.h:257) | 键盘释放事件 | ✅ 是 |
| [`on_char_input()`](src/widget/widget_base.h:258) | 字符输入事件 | ✅ 是 |
| [`on_mouse_down()`](src/widget/widget_base.h:248) | 鼠标按下 | ✅ 是 |
| [`on_mouse_up()`](src/widget/widget_base.h:249) | 鼠标释放 | ✅ 是 |
| [`on_mouse_move()`](src/widget/widget_base.h:250) | 鼠标移动 | ✅ 是 |
| [`on_focus_gained()`](src/widget/widget_base.h:261) | 获得焦点 | ✅ 是 |
| [`on_focus_lost()`](src/widget/widget_base.h:262) | 失去焦点 | ✅ 是 |
| [`is_focusable()`](src/widget/widget_base.h:293) | 是否可聚焦 | ✅ 需重写返回true |
| [`is_event_enabled()`](src/widget/widget_base.h:290) | 是否启用事件 | ✅ 是 |
### 1.3 leaf_widget_base 特性
[`leaf_widget_base`](src/widget/leaf_widget_base.h:16) 是叶子控件的基类,特点:
- 自动在 [`arrange()`](src/widget/leaf_widget_base.h:18) 中更新布局状态到外部存储
- 提供 [`get_intrinsic_size()`](src/widget/leaf_widget_base.h:35) 接口获取固有大小
- 提供 [`needs_repaint()`](src/widget/leaf_widget_base.h:40) 检查是否需要重绘
### 1.4 属性宏
框架提供了三个属性定义宏:
```cpp
WIDGET_ATTR(type, name) // 普通属性
WIDGET_RENDER_ATTR(type, name) // 改变时标记渲染脏
WIDGET_MEASURE_ATTR(type, name) // 改变时标记测量脏
```
## 2. 事件系统分析
### 2.1 事件路由器 widget_event_router
[`widget_event_router`](src/widget/widget_event/widget_event_router.h:59) 是事件系统的核心:
**职责:**
- 接收窗口层事件并分发到控件层
- 管理焦点、快捷键和 IME
- 实现事件冒泡机制
**事件处理顺序:**
1. 快捷键优先检查(仅 KEY_PRESSED 且非重复)
2. Tab 导航处理Tab/Shift+Tab
3. IME 事件处理
4. 分发到焦点控件(支持冒泡)
### 2.2 事件流向图
```
窗口层事件
widget_event_router
├─── 键盘事件 ────────────────────────────────────────┐
│ │ │
│ ▼ │
│ 快捷键检查 (shortcut_manager) │
│ │ (未匹配) │
│ ▼ │
│ Tab导航检查 (focus_manager) │
│ │ (不是Tab键) │
│ ▼ │
│ 分发到焦点控件 ──────────────────────────────────┤
│ │ │
│ ▼ │
│ 事件冒泡 (从焦点控件向上) │
│ │
├─── 字符输入事件 ────────────────────────────────────┤
│ │ │
│ ▼ │
│ 分发到焦点控件 │
│ │ │
│ ▼ │
│ 事件冒泡 │
│ │
└─── 鼠标事件 ────────────────────────────────────────┘
命中测试 (hit_test)
分发到命中控件
├─── 点击可聚焦控件 → 自动设置焦点
└─── 事件冒泡 (滚轮事件)
```
### 2.3 事件结果 event_result
[`event_result`](src/widget/widget_event/event_result.h:13) 结构用于控制事件处理:
```cpp
struct event_result {
bool is_handled : 1{false}; // 事件是否已处理(停止冒泡)
bool should_capture : 1{false}; // 请求捕获鼠标
bool should_release : 1{false}; // 请求释放鼠标捕获
// 工厂方法
static event_result unhandled(); // 未处理
static event_result handled(); // 已处理
static event_result capture(); // 已处理 + 捕获鼠标
static event_result release(); // 已处理 + 释放鼠标
};
```
### 2.4 键盘事件类型
[`keyboard_event`](src/widget/widget_event/event_types.h:37) 结构:
```cpp
struct keyboard_event {
key_action action; // press, release, repeat
key_code key; // 键码
int32_t scancode; // 扫描码
key_mod modifiers; // 修饰键状态
bool repeat; // 是否为重复事件
// 辅助方法
bool has_modifier(key_mod mod);
bool ctrl(), shift(), alt(), super();
};
```
### 2.5 字符输入事件
[`char_event`](src/widget/widget_event/event_types.h:85) 结构:
```cpp
struct char_event {
uint32_t codepoint; // UTF-32 字符码点
key_mod modifiers; // 修饰键状态
bool is_printable_ascii();
char to_ascii();
};
```
## 3. 焦点管理机制
### 3.1 focus_manager 功能
[`focus_manager`](src/widget/widget_event/focus_manager.h:67) 负责:
- 追踪当前焦点控件
- 维护焦点链(从根到焦点控件的路径)
- 支持 Tab 键导航
- 发布焦点变化事件
### 3.2 焦点相关接口
```cpp
// 设置焦点
bool set_focus(widget_base* target, focus_change_reason reason);
// 清除焦点
void clear_focus();
// 获取焦点控件
widget_base* get_focused_widget();
// Tab 导航
bool navigate_next();
bool navigate_previous();
// 注册可聚焦控件
void register_focusable(uint64_t widget_id, widget_base* target);
void register_tab_stop(widget_base* target, int32_t tab_index);
```
### 3.3 控件需要实现的焦点接口
```cpp
// 在 widget_base 中
virtual bool is_focusable() const { return false; } // 文本框需返回 true
virtual void on_focus_gained() {} // 显示光标
virtual void on_focus_lost() {} // 隐藏光标
```
## 4. IME 系统集成
### 4.1 ime_manager 功能
[`ime_manager`](src/widget/widget_event/ime_manager.h:73) 负责:
- 控制 IME 启用/禁用状态
- 管理预编辑文本
- 定位候选窗口
- 处理文本提交
- 发布 IME 相关事件
### 4.2 IME 状态
```cpp
enum class ime_state : uint8_t {
disabled, // IME 禁用
enabled, // IME 启用但未激活
composing // IME 正在预编辑
};
```
### 4.3 IME 事件订阅
```cpp
// 获取 IME 管理器
ime_manager& ime = router.get_ime_manager();
// 订阅预编辑事件
ime.on_preedit_start().subscribe(...);
ime.on_preedit_update().subscribe(...);
ime.on_preedit_end().subscribe(...);
// 订阅文本提交事件
ime.on_commit().subscribe(...);
```
### 4.4 预编辑信息
[`ime_preedit_info`](src/widget/widget_event/ime_types.h:49) 结构:
```cpp
struct ime_preedit_info {
std::string text; // 预编辑文本UTF-8
int32_t cursor_pos; // 光标位置
int32_t selection_start; // 选中起始
int32_t selection_length; // 选中长度
bool active; // 是否激活
};
```
### 4.5 设置候选窗口位置
```cpp
// 当文本框获得焦点或光标位置变化时
ime_manager& ime = router.get_ime_manager();
ime.set_candidate_position(
cursor_screen_x,
cursor_screen_y,
line_height
);
```
## 5. 文本渲染系统
### 5.1 text_shaper 功能
[`text_shaper`](src/render/text/text_shaper.h:28) 负责文本排版:
```cpp
// 排版文本,生成顶点数据
shaped_text shape_text(
std::u32string_view text,
font_id_t font_id,
float font_size,
const vec2f_t& position,
const color& text_color
);
// 测量文本尺寸(不生成顶点)
text_metrics measure_text(
std::u32string_view text,
font_id_t font_id,
float font_size
);
// UTF-8 转 UTF-32
static std::u32string utf8_to_utf32(std::string_view utf8);
```
### 5.2 shaped_text 结果
```cpp
struct shaped_text {
std::vector<ui_vertex> vertices;
std::vector<uint32_t> indices;
float total_width;
float total_height;
};
struct text_metrics {
float width;
float height;
};
```
### 5.3 render_collector 文本提交
通过 [`render_collector`](src/widget/render_collector.h:22) 提交文本渲染命令:
```cpp
void submit_text(
const vec2f_t& position,
std::string_view text,
const color& text_color,
float font_size,
uint32_t font_id = 0,
int32_t z_order = 0
);
```
## 6. 文本输入框设计建议
### 6.1 推荐继承方案
**方案 A继承 leaf_widget_base推荐**
```cpp
class text_input : public leaf_widget_base {
// 实现方式与 scrollbar 类似
};
```
优点:
- 简单直接,适合单一功能控件
- 布局状态管理自动处理
- 参考 [`scrollbar`](src/widget/widgets/scrollbar.h:57) 实现
**方案 B继承 composite_widget_base**
```cpp
class text_input : public composite_widget_base {
// 内部组合多个控件
};
```
适用场景:
- 需要内置滚动条的多行文本框
- 带有装饰元素(图标、清除按钮)的输入框
### 6.2 需要实现的关键方法
```cpp
class text_input : public leaf_widget_base {
public:
// ========================================================================
// 布局接口(必需)
// ========================================================================
[[nodiscard]] auto do_measure(const vec2f_t& available_size) const
-> vec2f_t override;
// arrange 使用 leaf_widget_base 默认实现
// ========================================================================
// 渲染接口(必需)
// ========================================================================
uint32_t build_render_command(render_collector& collector, uint32_t z_order)
const override;
// ========================================================================
// 焦点接口(必需)
// ========================================================================
[[nodiscard]] bool is_focusable() const override { return true; }
void on_focus_gained() override;
void on_focus_lost() override;
// ========================================================================
// 键盘事件(必需)
// ========================================================================
[[nodiscard]] event_result on_key_down(const keyboard_event& event) override;
[[nodiscard]] event_result on_key_up(const keyboard_event& event) override;
[[nodiscard]] event_result on_char_input(const char_event& event) override;
// ========================================================================
// 鼠标事件(必需)
// ========================================================================
[[nodiscard]] event_result on_mouse_down(const mouse_event& event) override;
[[nodiscard]] event_result on_mouse_up(const mouse_event& event) override;
[[nodiscard]] event_result on_mouse_move(const mouse_event& event) override;
// ========================================================================
// 可选接口
// ========================================================================
void tick(float dt) override; // 用于光标闪烁动画
[[nodiscard]] auto get_intrinsic_size() const
-> std::optional<vec2f_t> override;
};
```
### 6.3 IME 集成方式
```cpp
class text_input : public leaf_widget_base {
private:
ime_preedit_info preedit_; // 当前预编辑状态
public:
void on_focus_gained() override {
// 启用 IME
if (auto* ctx = get_context()) {
auto& ime = ctx->get_ime_manager();
ime.enable();
ime.set_focused_target(this);
update_candidate_position();
}
// 显示光标
show_cursor_ = true;
}
void on_focus_lost() override {
// 禁用 IME
if (auto* ctx = get_context()) {
auto& ime = ctx->get_ime_manager();
ime.disable();
ime.set_focused_target(nullptr);
}
// 隐藏光标
show_cursor_ = false;
// 清除预编辑状态
preedit_.clear();
}
void handle_ime_preedit(const ime_preedit_event& event) {
preedit_ = event.preedit;
mark_render_dirty();
}
void handle_ime_commit(const ime_commit_event& event) {
insert_text(event.text);
preedit_.clear();
mark_render_dirty();
}
void update_candidate_position() {
// 计算光标屏幕位置
auto cursor_pos = calculate_cursor_screen_position();
if (auto* ctx = get_context()) {
auto& ime = ctx->get_ime_manager();
ime.set_candidate_position(
static_cast<int32_t>(cursor_pos.x()),
static_cast<int32_t>(cursor_pos.y()),
static_cast<int32_t>(font_size_)
);
}
}
};
```
### 6.4 键盘事件处理示例
```cpp
event_result text_input::on_key_down(const keyboard_event& event) {
// 如果正在 IME 预编辑,部分按键需要特殊处理
if (preedit_.active) {
// Enter 确认预编辑
if (event.key == key_code::ENTER) {
// IME 会处理并发送 commit 事件
return event_result::handled();
}
// Escape 取消预编辑
if (event.key == key_code::ESCAPE) {
preedit_.clear();
mark_render_dirty();
return event_result::handled();
}
}
// 普通按键处理
switch (event.key) {
case key_code::LEFT:
move_cursor_left(event.shift());
return event_result::handled();
case key_code::RIGHT:
move_cursor_right(event.shift());
return event_result::handled();
case key_code::BACKSPACE:
delete_backward();
return event_result::handled();
case key_code::DELETE:
delete_forward();
return event_result::handled();
case key_code::HOME:
move_cursor_to_start(event.shift());
return event_result::handled();
case key_code::END:
move_cursor_to_end(event.shift());
return event_result::handled();
// Ctrl+A 全选
case key_code::A:
if (event.ctrl()) {
select_all();
return event_result::handled();
}
break;
// Ctrl+C 复制
case key_code::C:
if (event.ctrl()) {
copy_selection();
return event_result::handled();
}
break;
// Ctrl+V 粘贴
case key_code::V:
if (event.ctrl()) {
paste_from_clipboard();
return event_result::handled();
}
break;
// Ctrl+X 剪切
case key_code::X:
if (event.ctrl()) {
cut_selection();
return event_result::handled();
}
break;
default:
break;
}
return event_result::unhandled();
}
event_result text_input::on_char_input(const char_event& event) {
// 如果正在 IME 预编辑,忽略字符输入(由 IME 处理)
if (preedit_.active) {
return event_result::handled();
}
// 忽略控制字符
if (event.codepoint < 0x20) {
return event_result::unhandled();
}
// 插入字符
insert_codepoint(event.codepoint);
return event_result::handled();
}
```
### 6.5 渲染实现要点
```cpp
uint32_t text_input::build_render_command(
render_collector& collector,
uint32_t z_order
) const {
if (!should_render()) return z_order;
auto state = get_layout_state();
if (!state) return z_order;
vec2f_t pos = vec2f_t(state->global_pos());
vec2f_t size = vec2f_t(state->size());
// 1. 渲染背景
collector.submit_rect(pos, size, background_color_, corner_radius_, z_order++);
// 2. 渲染边框
// ...
// 3. 渲染选中区域(如果有)
if (has_selection()) {
auto sel_bounds = calculate_selection_bounds();
collector.submit_rect(sel_bounds.pos, sel_bounds.size,
selection_color_, {}, z_order++);
}
// 4. 渲染文本
vec2f_t text_pos = pos + padding_;
collector.submit_text(text_pos, text_, text_color_, font_size_,
font_id_, z_order++);
// 5. 渲染预编辑文本(如果有)
if (preedit_.active && !preedit_.text.empty()) {
// 计算预编辑文本位置
vec2f_t preedit_pos = calculate_preedit_position();
// 渲染预编辑背景(可选)
// 渲染预编辑文本
collector.submit_text(preedit_pos, preedit_.text, preedit_color_,
font_size_, font_id_, z_order++);
// 渲染预编辑下划线
// ...
}
// 6. 渲染光标(如果有焦点且光标可见)
if (has_focus() && cursor_visible_) {
auto cursor_pos = calculate_cursor_position();
collector.submit_rect(cursor_pos, {cursor_width_, font_size_},
cursor_color_, {}, z_order++);
}
return z_order;
}
```
## 7. 总结
### 7.1 文本输入框需要的核心能力
1. **焦点管理**:实现 `is_focusable()` 返回 true处理焦点获取/失去
2. **键盘输入**:处理方向键、删除键、快捷键等
3. **字符输入**:处理 `on_char_input` 事件
4. **IME 集成**:支持中文等复杂输入法
5. **鼠标交互**:支持点击定位光标、拖拽选择
6. **文本渲染**:使用 text_shaper 进行排版和渲染
7. **光标管理**:闪烁动画、位置计算
### 7.2 与现有系统的集成点
| 系统 | 集成方式 |
|------|----------|
| widget_base | 继承 leaf_widget_base |
| 事件路由 | 重写事件处理虚函数 |
| 焦点管理 | is_focusable() + 注册 tab_stop |
| IME | 订阅 ime_manager 事件 |
| 文本渲染 | 使用 text_shaper + render_collector |
| 状态管理 | 使用 WIDGET_*_ATTR 宏 |
### 7.3 参考实现
- [`text_widget`](src/widget/widgets/text_widget.h) - 静态文本显示
- [`scrollbar`](src/widget/widgets/scrollbar.h) - 交互式叶子控件示例

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,531 +0,0 @@
# Widget 框架线程同步重构计划
## 1. 概述
本文档详细描述了使用新线程同步框架重构 widget 框架的计划。目标是提高线程安全性、简化状态管理,并实现"无感同步"。
### 1.1 重构目标
1. **类型安全**:使用 `thread_bound<T>` 确保数据只在正确线程访问
2. **自动脏标记**:使用 `property<T>` 替代手动脏标记管理
3. **无锁同步**:使用 `sync_state<T>` 替代手动双缓冲管理
4. **事件解耦**:使用 `pub_sub` 替代回调函数
5. **任务调度**:使用 `thread_dispatcher` 统一跨线程任务调度
### 1.2 现有框架使用情况
| 组件 | 当前状态 | 已使用的线程同步特性 |
|------|----------|---------------------|
| `widget_base.h` | ✅ 部分使用 | `main_property<T>` 宏 |
| `thread_coordinator.h` | ✅ 部分使用 | `layout_to_render_state<T>`, `pub_sub` |
| `widget_state_store.h` | ❌ 未使用 | 手动脏标记管理 |
| `widget_context.h` | ❌ 未使用 | 直接引用访问 |
| `viewport_cache.h` | ❌ 未使用 | 手动缓存管理 |
| `render_collector.h` | ❌ 未使用 | 直接数据访问 |
| `scroll_box` 系列 | ❌ 未使用 | 手动状态管理 |
| 容器类 | ❌ 未使用 | 普通成员变量 |
---
## 2. 重构区域详细分析
### 2.1 区域一Widget 状态存储 (widget_state_store)
**当前问题:**
- 手动管理 `dirty_state` 枚举和脏标记
- `primary_state``secondary_state` 的线程安全依赖于调用者
- `layout_write_context` 使用手动锁管理
**重构方案:**
```cpp
// 当前实现
struct primary_state {
dirty_state dirty = dirty_state::measure_invalid;
bool renderable = true;
bool enabled = true;
// ...
};
// 重构后
struct primary_state_v2 {
threading::main_property<bool> renderable{true};
threading::main_property<bool> enabled{true};
threading::dirty_tracker dirty_tracker;
// 属性变化自动更新 dirty_tracker
};
```
**具体修改:**
| 文件 | 修改内容 | 优先级 |
|------|----------|--------|
| `widget_state.h` | 引入 `property<T>` 替代普通成员 | 高 |
| `widget_state_store.h` | 使用 `property_observer` 自动追踪变化 | 高 |
| `widget_state_store.cpp` | 重构 `layout_write_context` 使用 `sync_state` | 中 |
---
### 2.2 区域二Widget 上下文线程安全 (widget_context)
**当前问题:**
- `widget_context` 持有 `state_store``text_shaper` 的直接引用
- 任何线程都可以通过 `get_context()` 访问
- `viewport_cache` 的线程安全性不明确
**重构方案:**
```cpp
// 当前实现
class widget_context {
state::widget_state_store& store_;
widget::viewport_cache viewport_cache_;
render::text::text_shaper& text_shaper_;
};
// 重构后
class widget_context_v2 {
// 主线程只读,布局线程写入
threading::main_to_layout_state<viewport_info> viewport_state_;
// 使用线程绑定确保正确访问
threading::main_thread_bound<viewport_cache_accessor> viewport_accessor_;
// 状态存储使用新的线程安全封装
std::reference_wrapper<state::widget_state_store> store_;
};
```
**具体修改:**
| 文件 | 修改内容 | 优先级 |
|------|----------|--------|
| `widget_context.h` | 添加 `thread_bound` 封装关键资源 | 中 |
| `viewport_cache.h` | 使用 `sync_state` 管理可见性数据 | 中 |
| `viewport_cache.cpp` | 重构可见性更新逻辑 | 中 |
---
### 2.3 区域三:容器控件属性 (containers)
**当前问题:**
- `spacing_` 等属性使用普通 `float`
- 属性变化不会自动标记脏
- 链式调用返回 `auto&` 但不触发布局更新
**重构方案:**
```cpp
// 当前实现 (h_stack.h)
class h_stack {
float spacing_ = 0.0f;
public:
auto& spacing(float s) {
spacing_ = s;
return *this;
}
};
// 重构后
class h_stack_v2 {
WIDGET_MEASURE_ATTR(float, spacing) // 使用宏,自动标记 measure_dirty
};
```
**具体修改:**
| 文件 | 修改内容 | 优先级 |
|------|----------|--------|
| `h_stack.h` | `spacing_` 使用 `WIDGET_MEASURE_ATTR` | 低 |
| `v_stack.h` | `spacing_` 使用 `WIDGET_MEASURE_ATTR` | 低 |
| `overlay.h` | 无需修改(无额外属性)| - |
| `scroll_box.h` | `scroll_speed_`, `spacing_` 使用属性宏 | 低 |
---
### 2.4 区域四:滚动状态线程安全 (scroll_box)
**当前问题:**
- `scroll_state``offset_`, `viewport_size_` 等可能被多线程访问
- `smooth_scroll_animator` 的动画状态在 `tick()``animation_update()` 中修改
- 布局线程可能在动画进行时读取滚动偏移
**重构方案:**
```cpp
// 当前实现
class scroll_state {
vec2f_t offset_{0, 0};
vec2f_t max_offset_{0, 0};
vec2f_t viewport_size_{0, 0};
vec2f_t content_size_{0, 0};
};
// 重构后
class scroll_state_v2 {
// 主线程写入,布局线程读取
threading::main_to_layout_state<scroll_snapshot> scroll_sync_;
// 本地状态(仅主线程访问)
threading::main_property<vec2f_t> offset_{vec2f_t{0, 0}};
threading::main_property<vec2f_t> viewport_size_{vec2f_t{0, 0}};
// 发布滚动变化事件
void publish_scroll_change() {
scroll_sync_.modify([this](auto& snap) {
snap.offset = offset_.get();
snap.viewport_size = viewport_size_.get();
});
scroll_sync_.publish();
}
};
```
**具体修改:**
| 文件 | 修改内容 | 优先级 |
|------|----------|--------|
| `scroll_state.h` | 使用 `property<T>``sync_state` | 高 |
| `smooth_scroll_animator.h` | 确保动画状态线程安全 | 中 |
| `scrollbar_manager.h` | 使用 `property<T>` 管理滚动条状态 | 低 |
| `scroll_box.h` | 集成新的滚动状态管理 | 高 |
| `scroll_box.cpp` | 重构滚动逻辑使用 `sync_state` | 高 |
---
### 2.5 区域五:渲染收集器线程安全 (render_collector)
**当前问题:**
- `render_commands_` 直接存储,无线程保护
- `mask_stack_`, `clip_stack_` 在构建过程中被修改
- `viewport_cache_` 指针可能被多线程访问
**重构方案:**
```cpp
// 当前实现
class render_collector {
std::vector<render_command> render_commands_;
std::vector<mask_params> mask_stack_;
widget::viewport_cache* viewport_cache_ = nullptr;
};
// 重构后
class render_collector_v2 {
// 命令收集只在布局线程进行
threading::layout_thread_bound<std::vector<render_command>> render_commands_;
threading::layout_thread_bound<std::vector<mask_params>> mask_stack_;
// 视口缓存通过 sync_state 同步
std::shared_ptr<viewport_cache_sync> viewport_cache_sync_;
};
```
**具体修改:**
| 文件 | 修改内容 | 优先级 |
|------|----------|--------|
| `render_collector.h` | 添加 `thread_bound` 封装 | 中 |
| `render_collector.cpp` | 确保访问在正确线程 | 中 |
---
### 2.6 区域六:视口缓存同步 (viewport_cache)
**当前问题:**
- `cache_` 哈希表可能被多线程访问
- `visible_set_` 在布局过程中更新,渲染过程中读取
- `invalidate()` 可能从任意线程调用
**重构方案:**
```cpp
// 当前实现
class viewport_cache {
std::unordered_map<uint64_t, visibility_entry> cache_;
std::unordered_set<uint64_t> visible_set_;
bool valid_ = false;
};
// 重构后
class viewport_cache_v2 {
// 布局线程写入可见性数据
threading::layout_to_render_state<visibility_snapshot> visibility_sync_;
// 本地缓存(仅布局线程访问)
threading::layout_thread_bound<cache_data> local_cache_;
// 失效通知主题
threading::topic<cache_invalidate_event> invalidate_topic_;
};
```
**具体修改:**
| 文件 | 修改内容 | 优先级 |
|------|----------|--------|
| `viewport_cache.h` | 引入 `sync_state``pub_sub` | 中 |
| `viewport_cache.cpp` | 重构为线程安全实现 | 中 |
---
### 2.7 区域七:布局结果发布 (layout_state)
**当前问题:**
- `layout_state` 按值传递,每次布局都创建新对象
- 布局结果通过 `widget_state_store` 手动同步
- 没有布局完成的事件通知机制
**重构方案:**
```cpp
// 重构方案:使用 pub_sub 发布布局完成事件
struct layout_complete_event {
uint64_t widget_id;
layout_state new_state;
std::optional<aabb2d_t> old_aabb;
std::optional<aabb2d_t> new_aabb;
};
// 在 widget_state_store 中添加
class widget_state_store_v2 {
threading::topic<layout_complete_event> layout_complete_topic_;
void on_layout_updated(uint64_t widget_id, const layout_state& state) {
// ... 更新状态 ...
layout_complete_topic_.publish({widget_id, state, old_aabb, new_aabb});
}
};
```
**具体修改:**
| 文件 | 修改内容 | 优先级 |
|------|----------|--------|
| `widget_state_store.h` | 添加 `layout_complete_topic_` | 低 |
| `widget_state_store.cpp` | 在布局更新时发布事件 | 低 |
| `layout_state.h` | 无需修改 | - |
---
## 3. 重构执行计划
### 3.1 阶段一:核心状态管理重构(高优先级)
```mermaid
graph TD
A[阶段一开始] --> B[重构 widget_state.h]
B --> C[重构 widget_state_store.h/cpp]
C --> D[重构 scroll_state.h]
D --> E[重构 scroll_box.h/cpp]
E --> F[阶段一完成]
```
**预计工作量:** 3-5 天
**详细任务:**
1. **重构 `widget_state.h`** (1天)
-`dirty_state` 枚举与 `threading::dirty_flag` 对齐
- `primary_state` 使用 `property<T>` 包装 `renderable`, `enabled`
- 添加 `property_observer` 支持
2. **重构 `widget_state_store.h/cpp`** (1-2天)
- `layout_write_context` 使用更安全的 RAII 模式
- 添加基于 `pub_sub` 的状态变化通知
- 优化 `get_dirty_widgets()` 性能
3. **重构 `scroll_state.h`** (0.5天)
- `offset_`, `viewport_size_` 等使用 `property<T>`
- 添加 `sync_state` 用于跨线程同步
4. **重构 `scroll_box.h/cpp`** (0.5-1天)
- 集成新的 `scroll_state`
- 确保动画更新和布局访问的线程安全
---
### 3.2 阶段二:上下文和收集器重构(中优先级)
```mermaid
graph TD
A[阶段二开始] --> B[重构 widget_context.h]
B --> C[重构 viewport_cache.h/cpp]
C --> D[重构 render_collector.h/cpp]
D --> E[阶段二完成]
```
**预计工作量:** 2-3 天
**详细任务:**
1. **重构 `widget_context.h`** (0.5天)
- 添加线程安全访问器
- 使用 `thread_bound` 封装敏感资源
2. **重构 `viewport_cache.h/cpp`** (1天)
- 使用 `sync_state` 同步可见性数据
- 添加 `pub_sub` 用于失效通知
3. **重构 `render_collector.h/cpp`** (0.5-1天)
- 使用 `thread_bound` 确保正确线程访问
- 优化命令收集的线程安全性
---
### 3.3 阶段三:容器控件属性迁移(低优先级)
```mermaid
graph TD
A[阶段三开始] --> B[重构 h_stack.h]
B --> C[重构 v_stack.h]
C --> D[重构其他容器]
D --> E[阶段三完成]
```
**预计工作量:** 1-2 天
**详细任务:**
1. **重构容器属性** (1天)
- `h_stack::spacing_` 使用 `WIDGET_MEASURE_ATTR`
- `v_stack::spacing_` 使用 `WIDGET_MEASURE_ATTR`
- `scroll_box::scroll_speed_` 使用 `WIDGET_ATTR`
2. **测试和验证** (0.5-1天)
- 确保属性变化正确触发重布局
- 验证链式调用仍然有效
---
### 3.4 阶段四:事件系统集成(低优先级)
```mermaid
graph TD
A[阶段四开始] --> B[添加布局完成事件]
B --> C[添加渲染完成事件]
C --> D[集成到 thread_coordinator]
D --> E[阶段四完成]
```
**预计工作量:** 1 天
**详细任务:**
1. **添加事件发布** (0.5天)
- `widget_state_store` 添加布局完成主题
- 定义标准事件类型
2. **集成到协调器** (0.5天)
- `thread_coordinator` 订阅布局完成事件
- 触发渲染更新
---
## 4. 风险和注意事项
### 4.1 兼容性风险
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| API 变化 | 现有代码需要修改 | 保留旧 API 作为废弃接口 |
| 性能回归 | 额外的同步开销 | 使用 Release 模式验证零开销 |
| 线程死锁 | 锁顺序不一致 | 使用无锁数据结构 |
### 4.2 测试策略
1. **单元测试**
- 每个重构的类添加线程安全测试
- 使用 ThreadSanitizer 检测数据竞争
2. **集成测试**
- 多线程布局/渲染压力测试
- scroll_box 滚动动画测试
3. **性能测试**
- 对比重构前后的帧率
- 测量 `sync_state::publish()` 开销
### 4.3 回滚计划
- 每个阶段完成后创建 Git 标签
- 保留旧实现在 `_legacy` 后缀文件中
- 提供编译时开关切换新旧实现
---
## 5. 依赖关系图
```mermaid
graph TD
subgraph 核心原语层
A[thread_tags.h]
B[thread_context.h]
C[thread_bound.h]
end
subgraph 属性系统层
D[property.h]
E[computed_property.h]
F[property_observer.h]
end
subgraph 状态同步层
G[sync_state.h]
H[pub_sub.h]
I[thread_dispatcher.h]
end
subgraph Widget 层
J[widget_state.h]
K[widget_state_store.h]
L[widget_context.h]
M[widget_base.h]
N[scroll_state.h]
O[viewport_cache.h]
end
A --> B
B --> C
B --> D
D --> E
D --> F
B --> G
G --> H
H --> I
D --> J
J --> K
K --> L
L --> M
G --> N
G --> O
H --> K
```
---
## 6. 总结
### 6.1 重构优先级排序
1. **高优先级**`widget_state`, `widget_state_store`, `scroll_state`, `scroll_box`
2. **中优先级**`widget_context`, `viewport_cache`, `render_collector`
3. **低优先级**:容器控件属性迁移、事件系统集成
### 6.2 预计总工作量
| 阶段 | 工作量 | 累计 |
|------|--------|------|
| 阶段一 | 3-5 天 | 3-5 天 |
| 阶段二 | 2-3 天 | 5-8 天 |
| 阶段三 | 1-2 天 | 6-10 天 |
| 阶段四 | 1 天 | 7-11 天 |
### 6.3 成功指标
- [ ] 所有 Widget 属性使用 `property<T>` 管理
- [ ] 跨线程数据访问使用 `sync_state<T>``thread_bound<T>`
- [ ] 状态变化通知使用 `pub_sub` 机制
- [ ] 零数据竞争ThreadSanitizer 通过)
- [ ] 性能无明显回归(<5% 帧率下降)

View File

@@ -49,7 +49,7 @@ atlas_gpu_page::atlas_gpu_page(mirage::resource_manager& res_mgr, mirage::logica
allocation_ = image_result->allocation;
image_view_ = image_result->view;
// 创建采样器(线性过滤,边缘夹取)
// 创建采样器(线性过滤,边缘夹取,启用各向异性过滤以改善文字边缘质量
auto device_handle = device_->get_handle();
vk::SamplerCreateInfo sampler_info{};
sampler_info.magFilter = vk::Filter::eLinear;
@@ -57,8 +57,10 @@ atlas_gpu_page::atlas_gpu_page(mirage::resource_manager& res_mgr, mirage::logica
sampler_info.addressModeU = vk::SamplerAddressMode::eClampToEdge;
sampler_info.addressModeV = vk::SamplerAddressMode::eClampToEdge;
sampler_info.addressModeW = vk::SamplerAddressMode::eClampToEdge;
sampler_info.anisotropyEnable = VK_FALSE;
sampler_info.maxAnisotropy = 1.0f;
// 启用各向异性过滤以改善斜向边缘的渲染质量
// 对于MTSDF文字渲染适度的各向异性可以提升边缘清晰度
sampler_info.anisotropyEnable = VK_TRUE;
sampler_info.maxAnisotropy = 4.0f; // 适度的各向异性,平衡质量与性能
sampler_info.borderColor = vk::BorderColor::eFloatTransparentBlack;
sampler_info.unnormalizedCoordinates = VK_FALSE;
sampler_info.compareEnable = VK_FALSE;

View File

@@ -24,6 +24,7 @@ layout(location = 2) in vec2 frag_screen_pos; ///< 屏幕空间位置(预留
layout(location = 3) in vec2 frag_glyph_uv_size; ///< 字形在图集中的UV尺寸
layout(location = 4) in float frag_glyph_size; ///< 字形纹理大小(像素)
layout(location = 5) in flat int frag_page_index; ///< 图集页面索引flat插值
layout(location = 6) in float frag_pixel_range; ///< SDF像素范围动态传递
// ============================================================================
// 输出
@@ -48,20 +49,21 @@ layout(set = 0, binding = 0) uniform sampler2D u_atlas[16];
// ============================================================================
/**
* @brief MTSDF生成时使用的像素范围
* @brief 默认像素范围(仅在 frag_pixel_range <= 0 时使用)
*
* 这个值必须与 mtsdf_generator.cpp 中的 pixel_range 参数一致
* 默认值为6.0表示距离场覆盖字形边界周围6个像素的范围
* 正常情况下,pixel_range 通过顶点属性动态传递
* 此值作为后备默认值。
*/
const float PIXEL_RANGE = 6.0;
const float DEFAULT_PIXEL_RANGE = 6.0;
/**
* @brief 字形纹理大小(已弃用 - 现在通过顶点属性动态传递)
* @brief 获取有效的像素范围
*
* 此常量已被 frag_glyph_size 输入变量替代,该值从顶点着色器传递,
* 确保与实际生成的字形纹理大小一致。
* 优先使用动态传递的 frag_pixel_range如果无效则使用默认值。
*/
// const float GLYPH_TEXTURE_SIZE = 64.0; // 已弃用,使用 frag_glyph_size
float get_pixel_range() {
return frag_pixel_range > 0.0 ? frag_pixel_range : DEFAULT_PIXEL_RANGE;
}
// ============================================================================
// MTSDF核心算法
@@ -89,20 +91,51 @@ float median(float r, float g, float b) {
* 这用于自适应抗锯齿,确保在不同缩放级别下都能获得清晰的边缘。
*
* 使用字形的实际UV尺寸来计算unit_range
* unit_range = PIXEL_RANGE * glyph_uv_size / GLYPH_TEXTURE_SIZE
* unit_range = pixel_range * glyph_uv_size / glyph_texture_size
*
* @return 屏幕像素范围(用于距离场到屏幕像素的转换)
*/
float compute_screen_px_range() {
float pixel_range = get_pixel_range();
// 计算UV坐标在屏幕空间的变化率
// fwidth(uv) 返回 |dFdx(uv)| + |dFdy(uv)|,是两个方向导数之和
// 因此需要 * 0.5 来获得平均导数值
// 使用字形的实际UV尺寸和动态传递的字形纹理大小
vec2 unit_range = vec2(PIXEL_RANGE) * frag_glyph_uv_size / frag_glyph_size;
vec2 unit_range = vec2(pixel_range) * frag_glyph_uv_size / frag_glyph_size;
vec2 screen_tex_size = vec2(1.0) / fwidth(frag_uv);
return max(0.5 * dot(unit_range, screen_tex_size), 1.0);
}
/**
* @brief 计算自适应抗锯齿宽度
*
* 根据屏幕像素范围计算最优的AA过渡宽度。
* 对于高DPI显示器screen_px_range 较大),使用更窄的过渡带以获得更锐利的边缘。
* 对于低DPI或字体缩小的情况使用稍宽的过渡带以保持平滑。
*
* @param screen_px_range 屏幕像素范围
* @return 抗锯齿过渡宽度(像素)
*/
float compute_aa_width(float screen_px_range) {
float pixel_range = get_pixel_range();
// 基础AA宽度当screen_px_range等于pixel_range时使用0.7像素宽度(较锐利)
// 当screen_px_range较小缩小显示适当增加AA宽度以避免锯齿
// 当screen_px_range较大放大显示保持较窄的AA宽度以保持锐利
// 计算缩放因子screen_px_range / pixel_range
// > 1.0 表示放大(需要更锐利)
// < 1.0 表示缩小(需要更平滑)
float scale_factor = screen_px_range / pixel_range;
// AA宽度范围0.5(最锐利)到 1.2(最平滑)
// 使用平滑插值,避免突变
float aa_width = mix(1.2, 0.5, clamp(scale_factor, 0.5, 2.0) / 2.0);
return aa_width;
}
// ============================================================================
// 主函数
// ============================================================================
@@ -126,10 +159,10 @@ void main() {
// * screen_px_range: 转换为屏幕像素单位
float screen_px_distance = screen_px_range * (sd - 0.5);
// 使用更窄的抗锯齿过渡带0.7像素而不是1像素以获得更锐利的边缘
// 边界在 screen_px_distance = 0alpha = 0.5
const float AA_WIDTH = 1;
float alpha = clamp(screen_px_distance / AA_WIDTH + 0.5, 0.0, 1.0);
// 使用自适应抗锯齿宽度,根据屏幕像素密度动态调整
// 高DPI下使用更窄的过渡带以获得更锐利的边缘
float aa_width = compute_aa_width(screen_px_range);
float alpha = clamp(screen_px_distance / aa_width + 0.5, 0.0, 1.0);
// 输出最终颜色保留文本颜色的RGBalpha与距离场混合
out_color = vec4(frag_color.rgb, frag_color.a * alpha);

View File

@@ -47,6 +47,7 @@ layout(location = 2) out vec2 frag_screen_pos; ///< 屏幕空间位置(用于
layout(location = 3) out vec2 frag_glyph_uv_size; ///< 字形在图集中的UV尺寸
layout(location = 4) out float frag_glyph_size; ///< 字形纹理大小(像素)
layout(location = 5) out flat int frag_page_index; ///< 图集页面索引flat插值
layout(location = 6) out float frag_pixel_range; ///< SDF像素范围
// ============================================================================
// 主函数
@@ -74,4 +75,8 @@ void main() {
// 传递图集页面索引(存储在 data_b 的 x 分量中)
frag_page_index = int(in_data_b.x);
// 传递像素范围(存储在 data_a 的 w 分量中)
// 用于片段着色器中的距离场计算和自适应抗锯齿
frag_pixel_range = in_data_a.w;
}

View File

@@ -18,6 +18,7 @@ layout(location = 2) in vec2 frag_screen_pos; ///< 屏幕空间位置(预留
layout(location = 3) in vec2 frag_glyph_uv_size; ///< 字形在图集中的UV尺寸
layout(location = 4) in float frag_glyph_size; ///< 字形纹理大小(像素)
layout(location = 5) in flat int frag_page_index; ///< 图集页面索引flat插值
layout(location = 6) in float frag_pixel_range; ///< SDF像素范围动态传递
// ============================================================================
// 输出
@@ -50,16 +51,16 @@ layout(set = 1, binding = 0) uniform CustomParams {
// ============================================================================
/**
* @brief MTSDF生成时使用的像素范围
* 必须与 mtsdf_generator.cpp 中的 pixel_range 参数一致
* @brief 默认像素范围(仅在 frag_pixel_range <= 0 时使用)
*/
const float PIXEL_RANGE = 6.0;
const float DEFAULT_PIXEL_RANGE = 6.0;
/**
* @brief 字形纹理大小(已弃用 - 现在通过顶点属性动态传递)
* 此常量已被 frag_glyph_size 输入变量替代
* @brief 获取有效的像素范围
*/
// const float GLYPH_TEXTURE_SIZE = 64.0; // 已弃用,使用 frag_glyph_size
float get_pixel_range() {
return frag_pixel_range > 0.0 ? frag_pixel_range : DEFAULT_PIXEL_RANGE;
}
// ============================================================================
// MTSDF核心算法
@@ -77,26 +78,32 @@ float median(float r, float g, float b) {
// ============================================================================
void main() {
float pixel_range = get_pixel_range();
// 采样MTSDF纹理
vec4 mtsdf = texture(u_atlas, frag_uv);
float sd = median(mtsdf.r, mtsdf.g, mtsdf.b);
// 计算屏幕空间像素范围(用于抗锯齿)
// 使用字形的实际UV尺寸和动态传递的字形纹理大小
vec2 unit_range = vec2(PIXEL_RANGE) * frag_glyph_uv_size / frag_glyph_size;
vec2 unit_range = vec2(pixel_range) * frag_glyph_uv_size / frag_glyph_size;
vec2 screen_tex_size = vec2(1.0) / fwidth(frag_uv);
float screen_px_range = max(0.5 * dot(unit_range, screen_tex_size), 1.0);
// 计算自适应AA宽度
float scale_factor = screen_px_range / pixel_range;
float aa_width = mix(1.2, 0.5, clamp(scale_factor, 0.5, 2.0) / 2.0);
// 计算内部文字的alpha
// 标准阈值为0.5距离场值大于0.5的为字形内部
float inner_distance = (sd - 0.5) * screen_px_range;
float inner_alpha = clamp(inner_distance + 0.5, 0.0, 1.0);
float inner_alpha = clamp(inner_distance / aa_width + 0.5, 0.0, 1.0);
// 计算描边的alpha
// 通过向外扩展阈值来生成描边区域
// outline_width为正值时向外扩展负值时向内收缩
float outline_distance = (sd - 0.5 + params.outline_width) * screen_px_range;
float outline_alpha = clamp(outline_distance + 0.5, 0.0, 1.0);
float outline_alpha = clamp(outline_distance / aa_width + 0.5, 0.0, 1.0);
// 混合文字和描边
// 先绘制描边(外层),再绘制文字(内层)

View File

@@ -18,6 +18,7 @@ layout(location = 2) in vec2 frag_screen_pos; ///< 屏幕空间位置(预留
layout(location = 3) in vec2 frag_glyph_uv_size; ///< 字形在图集中的UV尺寸
layout(location = 4) in float frag_glyph_size; ///< 字形纹理大小(像素)
layout(location = 5) in flat int frag_page_index; ///< 图集页面索引flat插值
layout(location = 6) in float frag_pixel_range; ///< SDF像素范围动态传递
// ============================================================================
// 输出
@@ -50,16 +51,16 @@ layout(set = 1, binding = 0) uniform CustomParams {
// ============================================================================
/**
* @brief MTSDF生成时使用的像素范围
* 必须与 mtsdf_generator.cpp 中的 pixel_range 参数一致
* @brief 默认像素范围(仅在 frag_pixel_range <= 0 时使用)
*/
const float PIXEL_RANGE = 6.0;
const float DEFAULT_PIXEL_RANGE = 6.0;
/**
* @brief 字形纹理大小(已弃用 - 现在通过顶点属性动态传递)
* 此常量已被 frag_glyph_size 输入变量替代
* @brief 获取有效的像素范围
*/
// const float GLYPH_TEXTURE_SIZE = 64.0; // 已弃用,使用 frag_glyph_size
float get_pixel_range() {
return frag_pixel_range > 0.0 ? frag_pixel_range : DEFAULT_PIXEL_RANGE;
}
// ============================================================================
// MTSDF核心算法
@@ -77,6 +78,8 @@ float median(float r, float g, float b) {
// ============================================================================
void main() {
float pixel_range = get_pixel_range();
// 采样文字位置的MTSDF
vec4 mtsdf = texture(u_atlas, frag_uv);
float sd = median(mtsdf.r, mtsdf.g, mtsdf.b);
@@ -87,20 +90,24 @@ void main() {
// 计算屏幕空间像素范围(用于抗锯齿)
// 使用字形的实际UV尺寸和动态传递的字形纹理大小
vec2 unit_range = vec2(PIXEL_RANGE) * frag_glyph_uv_size / frag_glyph_size;
vec2 unit_range = vec2(pixel_range) * frag_glyph_uv_size / frag_glyph_size;
vec2 screen_tex_size = vec2(1.0) / fwidth(frag_uv);
float screen_px_range = max(0.5 * dot(unit_range, screen_tex_size), 1.0);
// 计算自适应AA宽度
float scale_factor = screen_px_range / pixel_range;
float aa_width = mix(1.2, 0.5, clamp(scale_factor, 0.5, 2.0) / 2.0);
// 计算文字的alpha
// 标准阈值为0.5
float text_distance = (sd - 0.5) * screen_px_range;
float text_alpha = clamp(text_distance + 0.5, 0.0, 1.0);
float text_alpha = clamp(text_distance / aa_width + 0.5, 0.0, 1.0);
// 计算阴影的alpha
// 通过shadow_softness调整阈值可以控制阴影的柔和程度
// softness越大阴影越柔和边缘越模糊
float shadow_distance = (shadow_sd - 0.5 - params.shadow_softness) * screen_px_range;
float shadow_alpha = clamp(shadow_distance + 0.5, 0.0, 1.0);
float shadow_alpha = clamp(shadow_distance / aa_width + 0.5, 0.0, 1.0);
// 阴影只在文字不存在的地方显示
// 使用(1.0 - text_alpha)确保阴影不会覆盖文字本身

View File

@@ -213,11 +213,12 @@ auto glyph_cache::get_or_request_glyph(
// 【关键】先建立映射再提交任务,避免竞争条件
// 生成任务ID需要先预留
// 使用配置的 pixel_range 比例计算实际值
glyph_generation_request request{
.font_path = font_path,
.glyph_index = glyph_index,
.glyph_size = glyph_size_,
.pixel_range = 6.0 // 默认像素范围
.pixel_range = static_cast<double>(glyph_config::calculate_pixel_range(glyph_size_))
};
glyph_task_id task_id = async_gen_->submit(request);
@@ -296,7 +297,7 @@ auto glyph_cache::process_completed_glyphs() -> bool {
}
if (result.success) {
// 提取度量信息
// 提取度量信息包含pixel_range用于着色器计算
glyph_metrics metrics{
.bearing = glm::vec2(result.bitmap.bearing_x, result.bitmap.bearing_y),
.advance = result.bitmap.advance_x,
@@ -304,7 +305,8 @@ auto glyph_cache::process_completed_glyphs() -> bool {
.inner_u0 = result.bitmap.inner_u0,
.inner_v0 = result.bitmap.inner_v0,
.inner_u1 = result.bitmap.inner_u1,
.inner_v1 = result.bitmap.inner_v1
.inner_v1 = result.bitmap.inner_v1,
.pixel_range = glyph_config::calculate_pixel_range(glyph_size_)
};
// 【关键修复】先更新 metrics再分配图集最后删除任务映射
@@ -361,15 +363,18 @@ auto glyph_cache::generate_and_add_glyph(
return std::nullopt; // 加载失败
}
// 生成MTSDF位图
auto bitmap_opt = mtsdf_generator::generate_mtsdf(glyph_slot, glyph_size_);
// 计算当前配置的 pixel_range
const double pixel_range = glyph_config::calculate_pixel_range(glyph_size_);
// 生成MTSDF位图使用配置的 pixel_range
auto bitmap_opt = mtsdf_generator::generate_mtsdf(glyph_slot, glyph_size_, pixel_range);
if (!bitmap_opt.has_value()) {
return std::nullopt; // 生成MTSDF失败
}
const auto& bitmap = bitmap_opt.value();
// 提取度量信息
// 提取度量信息包含pixel_range用于着色器计算
glyph_metrics metrics{
.bearing = glm::vec2(bitmap.bearing_x, bitmap.bearing_y),
.advance = bitmap.advance_x,
@@ -377,7 +382,8 @@ auto glyph_cache::generate_and_add_glyph(
.inner_u0 = bitmap.inner_u0,
.inner_v0 = bitmap.inner_v0,
.inner_u1 = bitmap.inner_u1,
.inner_v1 = bitmap.inner_v1
.inner_v1 = bitmap.inner_v1,
.pixel_range = static_cast<float>(pixel_range)
};
// 生成用户键 - 使用codepoint而不是glyph_index以保持与查找时的键一致

View File

@@ -14,6 +14,24 @@
namespace mirage::render::text {
/**
* @brief 字形渲染配置常量
*/
namespace glyph_config {
/// 默认字形纹理大小(像素)
/// 96px 提供比 64px 更高的精度适合高DPI显示
constexpr uint32_t default_glyph_size = 96;
/// pixel_range 占纹理大小的比例
/// 这个比例确保距离场有足够的精度同时不浪费空间
constexpr float pixel_range_ratio = 0.0625f; // 1/16
/// 计算给定纹理大小的 pixel_range
constexpr float calculate_pixel_range(uint32_t glyph_size) {
return static_cast<float>(glyph_size) * pixel_range_ratio;
}
}
/**
* @brief 字形度量信息
*
@@ -31,6 +49,9 @@ struct glyph_metrics {
float inner_v0 = 0.0f; ///< 字形在纹理中的下边界(归一化)
float inner_u1 = 1.0f; ///< 字形在纹理中的右边界(归一化)
float inner_v1 = 1.0f; ///< 字形在纹理中的上边界(归一化)
/// SDF像素范围用于着色器中的距离场计算
float pixel_range = 6.0f;
};
/**
@@ -72,12 +93,12 @@ public:
*
* @param font_mgr 字体管理器引用
* @param atlas_mgr 纹理图集管理器引用
* @param glyph_size 生成MTSDF的纹理尺寸默认64像素
* @param glyph_size 生成MTSDF的纹理尺寸默认96像素提供高DPI支持
*/
explicit glyph_cache(
font_manager& font_mgr,
texture_atlas_manager& atlas_mgr,
uint32_t glyph_size = 64
uint32_t glyph_size = glyph_config::default_glyph_size
);
/**
@@ -86,13 +107,13 @@ public:
* @param font_mgr 字体管理器引用
* @param atlas_mgr 纹理图集管理器引用
* @param async_gen 异步MTSDF生成器引用
* @param glyph_size 生成MTSDF的纹理尺寸默认64像素
* @param glyph_size 生成MTSDF的纹理尺寸默认96像素提供高DPI支持
*/
explicit glyph_cache(
font_manager& font_mgr,
texture_atlas_manager& atlas_mgr,
async_mtsdf_generator& async_gen,
uint32_t glyph_size = 64
uint32_t glyph_size = glyph_config::default_glyph_size
);
/**
@@ -186,7 +207,14 @@ public:
/**
* @brief 获取字形纹理大小
*/
auto get_glyph_size() const -> uint32_t { return glyph_size_; }
[[nodiscard]] auto get_glyph_size() const -> uint32_t { return glyph_size_; }
/**
* @brief 获取当前配置的pixel_range
*/
[[nodiscard]] auto get_pixel_range() const -> float {
return glyph_config::calculate_pixel_range(glyph_size_);
}
private:
/**

View File

@@ -13,15 +13,9 @@
namespace mirage::render::text {
namespace {
// 编译期常量
constexpr float BASE_GLYPH_SIZE = 64.0f;
constexpr size_t VERTICES_PER_GLYPH = 4;
constexpr size_t INDICES_PER_GLYPH = 6;
// 使用 consteval 确保编译期计算
consteval auto calculate_scale(float font_size) noexcept -> float {
return font_size / BASE_GLYPH_SIZE;
}
// 顶点位置索引
enum class vertex_corner : uint8_t {
top_left = 0,
@@ -58,8 +52,10 @@ namespace mirage::render::text {
};
}
const float scale = font_size / BASE_GLYPH_SIZE;
// 使用 glyph_cache 实际的 glyph_size 计算缩放因子
// 这确保与字形生成时使用的大小一致
const auto glyph_texture_size = static_cast<float>(cache_.get_glyph_size());
const float scale = font_size / glyph_texture_size;
// 获取字体的固定度量信息ascender确保任何文本内容的基线位置一致
// 这样无论输入 "abc" 还是 "ABC",文本的垂直位置都是稳定的
@@ -168,11 +164,17 @@ namespace mirage::render::text {
create_vertex(geom.x, geom.y + geom.h, final_u0, final_v1) // 左下
};
// 修正 data_a 字段UV 尺寸和纹理大小)
// 修正 data_a 字段
// - [0] UV 宽度
// - [1] UV 高度
// - [2] 字形纹理大小(像素)
// - [3] pixel_range用于着色器中的距离场计算
// 修正 data_b[0] 字段(图集页面索引,用于多页面支持)
for (auto& vertex : vertices) {
vertex.data_a[0] = geom.uv_width;
vertex.data_a[1] = geom.uv_height;
// data_a[2] 已在 create_vertex 中设置为 glyph_texture_size
vertex.data_a[3] = metrics.pixel_range; // 传递 pixel_range 到着色器
vertex.data_b[0] = static_cast<float>(atlas_entry.page_index); // 传递页面索引
}
@@ -216,7 +218,8 @@ namespace mirage::render::text {
return text_metrics{.width = 0.0f, .height = 0.0f};
}
const float scale = font_size / BASE_GLYPH_SIZE;
// 使用 glyph_cache 实际的 glyph_size 计算缩放因子
const float scale = font_size / static_cast<float>(cache_.get_glyph_size());
// 获取字体度量信息(与 shape_text 保持一致)
// 使用 ascender - descender 作为高度,确保测量与渲染一致

View File

@@ -28,13 +28,12 @@ static LRESULT CALLBACK ime_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPAR
GetPropA(hwnd, IME_HANDLER_PROP)
);
// 处理 WM_IME_SETCONTEXT 以完全禁用系统IME UI
// 处理 WM_IME_SETCONTEXT
if (msg == WM_IME_SETCONTEXT) {
if (wparam == TRUE) {
// 移除所有默认的IME UI标志完全由应用程序接管UI
// 只禁用系统预编辑框(因为应用自己绘制预编辑文本)
// 保留候选框的显示,让系统显示候选窗口
lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW; // 禁用系统预编辑框
lparam &= ~ISC_SHOWUICANDIDATEWINDOW; // 禁用系统候选框索引0
lparam &= ~ISC_SHOWUIALLCANDIDATEWINDOW; // 禁用所有候选框
lparam &= ~ISC_SHOWUIGUIDELINE; // 禁用指导线
}