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:
@@ -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: ~10MB(FreeType + 字体缓存)
|
||||
- 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调度
|
||||
- [ ] 性能分析和调优
|
||||
@@ -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) - 控件线程同步重构
|
||||
@@ -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. 添加手势识别支持
|
||||
@@ -1,360 +0,0 @@
|
||||
# IME (输入法编辑器) 跨平台实现文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了Mirage引擎的跨平台IME(Input 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
@@ -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
@@ -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
@@ -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
@@ -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% 帧率下降)
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = 0,alpha = 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);
|
||||
|
||||
// 输出最终颜色(保留文本颜色的RGB,alpha与距离场混合)
|
||||
out_color = vec4(frag_color.rgb, frag_color.a * alpha);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
// 混合文字和描边
|
||||
// 先绘制描边(外层),再绘制文字(内层)
|
||||
|
||||
@@ -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)确保阴影不会覆盖文字本身
|
||||
|
||||
@@ -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,以保持与查找时的键一致
|
||||
|
||||
@@ -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:
|
||||
/**
|
||||
|
||||
@@ -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 作为高度,确保测量与渲染一致
|
||||
|
||||
@@ -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; // 禁用指导线
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user