Files
mirage/docs/MODE_DATA_MIGRATION_GUIDE.md

20 KiB
Raw Permalink Blame History


⚠️ 此文档已废弃

Mode Data 层已被完全移除。本文档保留作为历史记录。

当前状态mode_data 相关的 API 和概念已不再使用,请参考新的架构文档。


Mode Data 接口迁移指南(归档)

📅 归档日期2025-01 🔒 状态:已废弃 - Mode Data 层已被移除

历史背景

本指南原用于描述从旧的 Mode Data 接口迁移到统一接口的过程。现在 Mode Data 层已被完全移除,本文档仅作为历史参考保留。

迁移历程

  1. v1.x:原有 mode_data 系统,包含 get_mode_data_ptr()is_mode_data_dirty()clear_mode_data_dirty() 等虚函数
  2. v2.xmode_data 层移除,所有相关接口已废弃
  3. 当前mode_data 概念完全移除,相关代码已重构

架构变化摘要

移除的组件

组件 说明 替代方案
mode_data_mixin CRTP Mixin 用于 Mode Data 管理 已移除
get_mode_data_ptr() 基类虚函数获取 Mode Data 使用 get_params_data() 替代
is_mode_data_dirty() Mode Data 脏标志检查 使用 is_params_dirty() 替代
clear_mode_data_dirty() 清除 Mode Data 脏标志 使用 clear_params_dirty() 替代
compute_mode_data() 统一计算 Mode Data 已移除
mode_data() 获取 Mode Data 引用 已移除

保留的组件

组件 说明
get_params_data() 获取 UBO 参数数据
is_params_dirty() 参数脏标志检查
clear_params_dirty() 清除参数脏标志
get_push_constant_effect_data() Push Constants 效果参数
get_effect_rect() 效果区域

新的架构

当前架构使用简化的参数管理:

class custom_shader_widget_base : public leaf_widget_base {
protected:
    // UBO 参数(必需)
    [[nodiscard]] virtual auto get_params_data() const
        -> std::pair<const void*, size_t> = 0;
    [[nodiscard]] virtual auto is_params_dirty() const noexcept -> bool = 0;
    virtual void clear_params_dirty() = 0;

    // Push Constants 效果参数(可选)
    [[nodiscard]] virtual auto get_push_constant_effect_data() const
        -> std::pair<const void*, size_t>;
    [[nodiscard]] virtual auto is_push_constant_effect_dirty() const noexcept -> bool;
    virtual void clear_push_constant_effect_dirty();

    // 效果区域(可选)
    [[nodiscard]] virtual auto get_effect_rect() const noexcept -> vec4f_t;
    virtual void set_effect_rect(const vec4f_t& rect);
};

常见问题

Q: 我的代码使用了 mode_data 相关的 API 怎么办?

A: 这些 API 已被移除,请使用以下替代方案:

旧 API 新 API
get_mode_data_ptr() get_params_data()
mode_data() params()
mark_mode_data_dirty() mark_params_dirty()

Q: 如何获取控件位置和大小信息?

A: 使用标准的布局接口:

// 获取全局位置
auto pos = widget->get_global_position();

// 获取大小
auto size = widget->get_size();

// 获取效果区域(用于 post_process 模式)
auto rect = widget->get_effect_rect();

相关文档


文档版本: 2.0(归档版) 最后更新: 2025-01


3. 逐类迁移指南

3.1 基类变更:custom_shader_widget_base

基类新增了三个纯虚函数,所有自定义子类必须实现:

// 新增的纯虚函数(位于 custom_shader_widget_base.h:45-53
[[nodiscard]] virtual auto get_mode_data_ptr() const
    -> std::pair<const void*, size_t> = 0;

[[nodiscard]] virtual auto is_mode_data_dirty() const noexcept -> bool = 0;

virtual void clear_mode_data_dirty() = 0;

如果您直接继承 custom_shader_widget_base:需要实现这三个方法。

如果您继承模板子类(推荐):这些方法已由模板类实现,无需额外操作。


3.2 procedural_shader_widget<ShaderSpec>

方法重命名

旧方法 新方法 说明
widget_color_component(i) widget_color_at(i) + set_widget_color_at(i, v) 分离读写
get_procedural_mode_data() compute_mode_data() 统一命名

新增方法

迁移示例

读取颜色分量:

// ❌ 旧代码(已弃用)
float r = widget.widget_color_component(0);

// ✅ 新代码
float r = widget.widget_color_at(0);

// ✅ 或直接访问整个数组
auto color = widget.get_widget_color();
float r = color[0];

修改颜色分量:

// ❌ 旧代码(已弃用)
widget.widget_color_component(0) = 1.0f;
widget.mark_mode_data_dirty();  // 容易忘记调用

// ✅ 新代码(自动标记脏标志)
widget.set_widget_color_at(0, 1.0f);

获取 Mode Data

// ❌ 旧代码(已弃用)
auto data = widget.get_procedural_mode_data();

// ✅ 新代码
auto data = widget.compute_mode_data();

3.3 post_process_shader_widget<ShaderSpec>

方法重命名

旧方法 新方法 说明
source_uv_component(i) source_uv_at(i) + set_source_uv_at(i, v) 分离读写
get_post_process_mode_data() compute_mode_data() 统一命名

新增方法

迁移示例

读取 UV 分量:

// ❌ 旧代码(已弃用)
float u0 = widget.source_uv_component(0);

// ✅ 新代码
float u0 = widget.source_uv_at(0);

// ✅ 或使用完整 UV rect
auto uv = widget.get_source_uv_rect();

修改 UV 分量:

// ❌ 旧代码(已弃用)
widget.source_uv_component(2) = 0.5f;
widget.mark_mode_data_dirty();

// ✅ 新代码(自动标记脏标志)
widget.set_source_uv_at(2, 0.5f);

获取 Mode Data

// ❌ 旧代码(已弃用)
auto data = widget.get_post_process_mode_data();

// ✅ 新代码
auto data = widget.compute_mode_data();

3.4 mask_shader_widget<ShaderSpec>

方法重命名

旧方法 新方法 说明
corner_radius_component(i) corner_radius_at(i) + set_corner_radius_at(i, v) 分离读写
get_mask_mode_data() compute_mode_data() 统一命名

新增方法

访问级别变更

  • is_mode_data_dirty() - 从 protected 改为 public
  • clear_mode_data_dirty() - 从 protected 改为 public

迁移示例

读取圆角半径:

// ❌ 旧代码(已弃用)
float tl = widget.corner_radius_component(0);

// ✅ 新代码
float tl = widget.corner_radius_at(0);

// ✅ 或使用完整数组
auto radii = widget.get_corner_radii();

修改圆角半径:

// ❌ 旧代码(已弃用)
widget.corner_radius_component(0) = 10.0f;
widget.mark_mode_data_dirty();

// ✅ 新代码(自动标记脏标志)
widget.set_corner_radius_at(0, 10.0f);

// ✅ 或设置所有四角
widget.set_corner_radii(10.0f, 10.0f, 10.0f, 10.0f);
widget.set_uniform_corner_radius(10.0f);  // 等效

获取 Mode Data

// ❌ 旧代码(已弃用)
auto data = widget.get_mask_mode_data();

// ✅ 新代码
auto data = widget.compute_mode_data();

3.5 full_custom_shader_widget<ShaderSpec, VertexT>

方法重命名

旧方法 新方法 说明
user_data() user_data_at(i) + set_user_data_at(i, v) 分离读写
set_user_data(i, v) set_user_data_at(i, v) 命名统一
get_full_custom_mode_data() compute_mode_data() 统一命名

新增方法

迁移示例

读取用户数据:

// ❌ 旧代码(已弃用)
float val = widget.user_data()[0];

// ✅ 新代码
float val = widget.user_data_at(0);

修改用户数据:

// ❌ 旧代码(已弃用)
widget.user_data()[0] = 42.0f;
widget.mark_mode_data_dirty();

// ❌ 旧代码(已弃用)
widget.set_user_data(0, 42.0f);

// ✅ 新代码(自动标记脏标志)
widget.set_user_data_at(0, 42.0f);

获取 Mode Data

// ❌ 旧代码(已弃用)
auto data = widget.get_full_custom_mode_data();

// ✅ 新代码
auto data = widget.compute_mode_data();

4. 常见迁移场景

场景 1读取数组元素

// ===== procedural_shader_widget =====
// 旧:返回引用,无边界检查
float r = widget.widget_color_component(0);
// 新:返回值,带边界检查
float r = widget.widget_color_at(0);

// ===== post_process_shader_widget =====
// 旧
float u = widget.source_uv_component(0);
// 新
float u = widget.source_uv_at(0);

// ===== mask_shader_widget =====
// 旧
float radius = widget.corner_radius_component(0);
// 新
float radius = widget.corner_radius_at(0);

// ===== full_custom_shader_widget =====
// 旧
float data = widget.user_data()[0];
// 新
float data = widget.user_data_at(0);

场景 2修改数组元素

// ===== 通用模式 =====
// 旧:通过引用修改,需手动标记脏
widget.xxx_component(i) = value;
widget.mark_mode_data_dirty();

// 新:通过 setter 修改,自动标记脏
widget.set_xxx_at(i, value);

场景 3遍历数组

// ===== procedural_shader_widget =====
// 旧
for (size_t i = 0; i < 4; ++i) {
    process(widget.widget_color_component(i));
}

// 新:推荐使用完整数组访问
auto color = widget.get_widget_color();
for (float c : color) {
    process(c);
}

// 或使用索引访问(带边界检查)
for (size_t i = 0; i < 4; ++i) {
    process(widget.widget_color_at(i));
}

场景 4自定义子类实现

如果您有直接继承 custom_shader_widget_base 的自定义类,需要实现新增的纯虚函数:

class my_custom_widget : public custom_shader_widget_base {
public:
    // 必须实现这三个纯虚函数

    [[nodiscard]] auto get_mode_data_ptr() const
        -> std::pair<const void*, size_t> override {
        return {&my_mode_data_, sizeof(my_mode_data_)};
    }

    [[nodiscard]] auto is_mode_data_dirty() const noexcept -> bool override {
        return mode_data_dirty_;
    }

    void clear_mode_data_dirty() override {
        mode_data_dirty_ = false;
    }

private:
    my_mode_data_type my_mode_data_{};
    bool mode_data_dirty_ = true;
};

5. 渲染器迁移

5.1 使用新的 get_mode_data_ptr() 虚函数

旧渲染器代码可能需要针对不同模式做类型判断:

// ❌ 旧代码:需要类型判断
if (mode == procedural) {
    auto data = static_cast<procedural_widget*>(widget)->get_procedural_mode_data();
    upload_push_constants(&data, sizeof(data));
} else if (mode == post_process) {
    auto data = static_cast<post_process_widget*>(widget)->get_post_process_mode_data();
    upload_push_constants(&data, sizeof(data));
}
// ... 更多模式判断

新代码可以统一处理:

// ✅ 新代码:通过基类统一访问
auto [data_ptr, data_size] = widget->get_mode_data_ptr();
if (data_ptr && data_size > 0) {
    upload_push_constants(data_ptr, data_size);
}

5.2 脏标志管理

渲染器可以利用 is_mode_data_dirty() 优化更新:

// 仅在数据变化时更新 GPU
if (widget->is_mode_data_dirty()) {
    auto [data_ptr, data_size] = widget->get_mode_data_ptr();
    update_gpu_buffer(data_ptr, data_size);
    widget->clear_mode_data_dirty();
}

5.3 渲染器实现参考

void custom_shader_widget_renderer::update_mode_data(
    custom_shader_widget_base* widget,
    vk::CommandBuffer cmd
) {
    // 1. 检查是否需要更新
    if (!widget->is_mode_data_dirty()) {
        return;
    }

    // 2. 获取 Mode DataLayer 2
    auto [mode_data, mode_data_size] = widget->get_mode_data_ptr();

    // 3. 上传 Push Constants
    if (mode_data && mode_data_size > 0) {
        cmd.pushConstants(
            pipeline_layout_,
            vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
            push_constant_offsets::MODE_DATA,  // offset 80
            static_cast<uint32_t>(mode_data_size),
            mode_data
        );
    }

    // 4. 清除脏标志
    widget->clear_mode_data_dirty();
}

6. 测试验证清单

完成迁移后,请验证以下功能点:

6.1 编译检查

  • 无弃用警告(使用 -Werror=deprecated-declarations 检查)
  • 所有自定义子类正确实现纯虚函数
  • 模板实例化无错误

6.2 功能测试

  • procedural 模式

    • widget_color_at() 正确读取颜色
    • set_widget_color_at() 正确修改颜色并标记脏
    • compute_mode_data() 返回正确的 widget_rect
  • post_process 模式

    • source_uv_at() 正确读取 UV
    • set_source_uv_at() 正确修改 UV 并标记脏
    • compute_mode_data() 返回正确的数据
  • mask 模式

    • corner_radius_at() 正确读取半径
    • set_corner_radius_at() 正确修改半径并标记脏
    • compute_mode_data() 自动计算 mask_center 和 mask_size
  • full_custom 模式

    • user_data_at() 正确读取数据
    • set_user_data_at() 正确修改数据并标记脏
    • 顶点/索引缓冲正常工作

6.3 渲染器测试

  • get_mode_data_ptr() 返回正确的指针和大小
  • is_mode_data_dirty() 正确反映状态
  • clear_mode_data_dirty() 正确清除标志
  • Push Constants 正确上传到 GPU
  • 渲染结果与迁移前一致

6.4 边界检查测试

  • 越界索引抛出 std::out_of_range
  • 错误信息包含有效索引范围
// 测试代码示例
try {
    widget.widget_color_at(4);  // 应该抛出异常
    FAIL("Expected out_of_range exception");
} catch (const std::out_of_range& e) {
    EXPECT_THAT(e.what(), HasSubstr("index"));
}

7. 常见问题解答FAQ

Q1: 为什么要弃用 *_component() 方法?

A: 旧方法存在几个问题:

  1. 返回引用无法进行边界检查,可能导致未定义行为
  2. 修改后需要手动调用 mark_mode_data_dirty(),容易遗忘
  3. 命名不够清晰(component vs at

新方法分离读写操作setter 自动标记脏标志,更加安全和便捷。


Q2: 新的 *_at() 方法性能如何?

A: 边界检查的开销极小(一次比较和条件跳转),在 Release 构建中对性能影响可忽略。安全性的提升远超这点开销。

如果您确实需要零开销访问,可以使用:

// 通过 mode_data() 直接访问底层数组(无边界检查)
const auto& data = widget.mode_data();
float r = data.widget_color[0];  // 直接访问

Q3: 可以同时使用新旧 API 吗?

A: 可以,但不建议。混用可能导致:

  1. 代码风格不一致
  2. 部分修改漏调用 mark_mode_data_dirty()
  3. 增加维护成本

建议:在同一个文件或类中统一使用新 API。


Q4: 如何快速查找需要迁移的代码?

A: 使用正则表达式搜索:

# 查找 widget_color_component 调用
grep -rn "widget_color_component" src/

# 查找 source_uv_component 调用
grep -rn "source_uv_component" src/

# 查找 corner_radius_component 调用
grep -rn "corner_radius_component" src/

# 查找旧的 user_data 用法
grep -rn "user_data()" src/

# 查找旧的 get_*_mode_data 调用
grep -rn "get_procedural_mode_data\|get_post_process_mode_data\|get_mask_mode_data\|get_full_custom_mode_data" src/

或者启用编译器的弃用警告:

# GCC/Clang
cmake -DCMAKE_CXX_FLAGS="-Wdeprecated-declarations" ..

# MSVC
cmake -DCMAKE_CXX_FLAGS="/W4" ..

Q5: mode_data() 返回的是只读引用,如何批量修改?

A: 有几种方式:

// 方式1使用专用的批量设置方法
widget.set_widget_color(1.0f, 0.5f, 0.0f, 1.0f);
widget.set_corner_radii(10.0f, 10.0f, 10.0f, 10.0f);

// 方式2使用数组参数
std::array<float, 4> color = {1.0f, 0.5f, 0.0f, 1.0f};
widget.set_widget_color(color);

// 方式3链式调用单个设置
widget.set_widget_color_at(0, 1.0f)
      .set_widget_color_at(1, 0.5f)
      .set_widget_color_at(2, 0.0f)
      .set_widget_color_at(3, 1.0f);

Q6: 子类如何重写 compute_mode_data()

A: compute_mode_data() 是一个虚函数,子类可以重写以自定义行为:

class my_gradient_widget : public procedural_shader_widget<gradient_spec> {
protected:
    [[nodiscard]] auto compute_mode_data() const -> procedural_mode_data override {
        // 调用基类实现
        auto data = procedural_shader_widget::compute_mode_data();
        
        // 自定义修改
        data.widget_color[3] = calculate_dynamic_alpha();
        
        return data;
    }
};

Q7: 迁移后旧代码还能编译吗?

A: 能,但会产生编译器警告。示例:

warning: 'widget_color_component' is deprecated: Use widget_color_at(index) 
or set_widget_color_at(index, value) instead [-Wdeprecated-declarations]

Q8: 弃用的 API 什么时候会被移除?

A: 计划在 v3.0 版本移除。建议:

  • 现在开始逐步迁移
  • 在 v2.x 周期内完成所有迁移
  • 使用 CI 检测弃用警告,防止新代码使用旧 API

附录:变更速查表

基类纯虚函数

方法 返回类型 说明
get_mode_data_ptr() std::pair<const void*, size_t> 获取 Mode Data 原始指针和大小
is_mode_data_dirty() bool 检查 Mode Data 是否需要更新
clear_mode_data_dirty() void 清除脏标志

方法重命名对照表

procedural_shader_widget

旧方法 新方法(读取) 新方法(写入)
widget_color_component(i) widget_color_at(i) set_widget_color_at(i, v)
get_procedural_mode_data() compute_mode_data() -

post_process_shader_widget

旧方法 新方法(读取) 新方法(写入)
source_uv_component(i) source_uv_at(i) set_source_uv_at(i, v)
get_post_process_mode_data() compute_mode_data() -

mask_shader_widget

旧方法 新方法(读取) 新方法(写入)
corner_radius_component(i) corner_radius_at(i) set_corner_radius_at(i, v)
get_mask_mode_data() compute_mode_data() -

full_custom_shader_widget

旧方法 新方法(读取) 新方法(写入)
user_data() (数组) user_data_at(i) set_user_data_at(i, v)
set_user_data(i, v) - set_user_data_at(i, v)
get_full_custom_mode_data() compute_mode_data() -

新增公开方法

方法 说明
所有模板类 mode_data() 获取 Mode Data 只读引用
所有模板类 mark_mode_data_dirty() 标记 Mode Data 已修改

访问级别变更

方法 旧访问级别 新访问级别
mask_shader_widget is_mode_data_dirty() protected public
mask_shader_widget clear_mode_data_dirty() protected public
post_process_shader_widget is_mode_data_dirty() protected public
post_process_shader_widget clear_mode_data_dirty() protected public

文档版本: 1.0 最后更新: 2025-01