Files
mirage/plans/instance_data_auto_generation_design.md
2025-12-25 13:20:16 +08:00

21 KiB
Raw Permalink Blame History

实例数据自动生成方案设计文档

版本: 1.0 日期: 2025-01 状态: 设计草案 关联文档: instanced_rendering_api_design.md


目录

  1. 背景与目标
  2. 现有工具分析
  3. 设计方案
  4. 着色器语法设计
  5. 错误处理策略
  6. 实现步骤清单
  7. 与现有 shader_spec 的集成

1. 背景与目标

1.1 背景

用户反馈:实例化渲染的数据结构不应该手动定义,而应该由着色器绑定代码工具自动生成。

当前问题:

  • 手动定义实例数据结构容易出错
  • 与着色器中的实际布局可能不匹配
  • 增加了维护负担

1.2 设计目标

目标 描述 优先级
自动生成 从着色器 SPIR-V 自动提取实例数据布局
带宽优化 根据实际着色器需要生成最小必要字段
一致性 与现有 xxx_shader_spec 生成流程保持一致
早期错误检测 在工具编译阶段检测并报告布局错误
类型安全 生成代码中添加编译期静态检查

2. 现有工具分析

2.1 代码生成流程图

flowchart TB
    subgraph 输入
        GLSL[着色器源文件<br>.vert.glsl / .frag.glsl]
    end
    
    subgraph CMake编译流程
        CMAKE[compile_shaders.cmake]
        PATHS[shader_paths.txt]
    end
    
    subgraph Python工具链
        MAIN[tools/main.py<br>主入口]
        COMPILER[tools/compiler.py<br>调用 glslc]
        PARSER[tools/spirv_parser.py<br>SPIR-V 解析]
        TYPEMAP[tools/type_mapping.py<br>类型映射]
        CODEGEN[tools/code_generator.py<br>代码生成]
        TEMPLATE[tools/templates/<br>Jinja2 模板]
    end
    
    subgraph 输出
        SPIRV[SPIR-V 二进制]
        HEADER[生成的 C++ 头文件<br>xxx_vert_frag.h]
    end
    
    GLSL --> CMAKE
    CMAKE --> PATHS
    PATHS --> MAIN
    MAIN --> COMPILER
    COMPILER --> SPIRV
    SPIRV --> PARSER
    PARSER --> TYPEMAP
    PARSER --> CODEGEN
    TYPEMAP --> CODEGEN
    CODEGEN --> TEMPLATE
    TEMPLATE --> HEADER

2.2 SPIR-V 反射机制

tools/spirv_parser.py 实现了完整的 SPIR-V 反射:

类型 解析函数 生成的 TypeInfo
标量 OP_TYPE_INT, OP_TYPE_FLOAT ScalarTypeInfo
向量 OP_TYPE_VECTOR VectorTypeInfo
矩阵 OP_TYPE_MATRIX MatrixTypeInfo
数组 OP_TYPE_ARRAY, OP_TYPE_RUNTIME_ARRAY ArrayTypeInfo
结构体 OP_TYPE_STRUCT StructTypeInfo
指针 OP_TYPE_POINTER PointerTypeInfo

2.3 Push Constants 布局规范

当前 Push Constants 布局128 字节):

┌─────────────────────────────────────────────────────────────────────────────┐
│ Push Constants 内存布局 (128 bytes)                                         │
├─────────────────────────────────────────────────────────────────────────────┤
│ Header [0-15]:    scale (vec2) + translate (vec2)     - 16 bytes           │ ← 系统填充
│ Custom [16-127]:  用户自定义数据                      - 112 bytes          │ ← 自动生成
└─────────────────────────────────────────────────────────────────────────────┘

3. 设计方案

3.1 方案概述

从专门标记的 Instance SSBO 生成实例数据结构。

优点

  • 无大小限制
  • 明确的语义(通过注释显式标记)
  • 支持 Runtime Array
  • 着色器作者意图明确

数据分类

数据类别 来源 存储位置 特点
共享参数 UBO Uniform Buffer 所有实例共享,如渐变结束颜色
每实例参数 标记的 SSBO Storage Buffer 每个实例不同,如位置、颜色
系统变换 Push Constants Header Push Constants [0-15] scale, translate

3.2 工作流程

flowchart TB
    SHADER[着色器源文件] --> GLSLC[glslc 编译]
    GLSLC --> SPIRV[SPIR-V 二进制]
    SPIRV --> PARSER[spirv_parser.py]
    
    PARSER --> DETECT{检测 @instance_buffer<br>注释标记}
    
    DETECT -->|找到| VALIDATE[验证布局]
    DETECT -->|未找到| SKIP[跳过实例数据生成]
    
    VALIDATE -->|验证通过| GENERATE[生成实例数据结构]
    VALIDATE -->|验证失败| ERROR[工具报错退出]
    
    GENERATE --> CODEGEN[code_generator.py]
    CODEGEN --> HEADER[生成 C++ 头文件]
    
    HEADER --> STATIC[C++ 编译期静态检查<br>二次验证]

4. 着色器语法设计

4.1 显式标记语法

通过特殊注释 @instance_buffer 显式标记实例缓冲:

// @instance_buffer
layout(std430, set = 0, binding = N) readonly buffer SomeBuffer {
    SomeData items[];
};

规则

  • 注释必须紧邻 SSBO 声明(在 layout 之前)
  • 标记的 SSBO 必须包含 Runtime Array
  • 一个着色器最多标记一个实例缓冲

4.2 必需字段要求

工具会验证标记的实例数据结构包含 rect 字段:

struct InstanceData {
    vec4 rect;           // 必需x, y, width, height偏移必须为 0
    // ... 其他用户定义字段
};

验证规则

  1. 第一个成员必须命名为 rect
  2. 类型必须为 vec4
  3. 偏移必须为 0

4.3 完整着色器示例

#version 450 core

// ============================================================================
// Push Constants共享变换
// ============================================================================

layout(push_constant) uniform PushConstants {
    vec2 scale;      // NDC 缩放(系统填充)
    vec2 translate;  // NDC 平移(系统填充)
} pc;

// ============================================================================
// 实例数据 SSBO
// ============================================================================

struct GradientInstanceData {
    vec4 rect;           // 必需x, y, width, height
    vec4 start_color;    // 起始颜色(每实例不同)
    vec4 transform;      // rotation, anchor_x, anchor_y, scale
    vec4 custom_params;  // 自定义参数
};

// @instance_buffer
layout(std430, set = 0, binding = 0) readonly buffer InstanceBuffer {
    GradientInstanceData instances[];
};

// ============================================================================
// 共享参数 UBO
// ============================================================================

layout(set = 0, binding = 1) uniform GradientSharedParams {
    vec4 end_color;       // 结束颜色(所有实例共享)
    float gradient_type;  // 渐变类型
    float gradient_angle; // 渐变角度
    vec2 _padding;
};

// ============================================================================
// 顶点着色器主函数
// ============================================================================

layout(location = 0) out vec2 v_uv;
layout(location = 1) out vec4 v_start_color;
layout(location = 2) flat out uint v_instance_id;

const vec2 QUAD_POSITIONS[4] = vec2[4](
    vec2(0.0, 0.0), vec2(1.0, 0.0),
    vec2(0.0, 1.0), vec2(1.0, 1.0)
);
const int QUAD_INDICES[6] = int[6](0, 1, 2, 2, 1, 3);

mat2 rotate2D(float angle) {
    float c = cos(angle);
    float s = sin(angle);
    return mat2(c, -s, s, c);
}

void main() {
    GradientInstanceData inst = instances[gl_InstanceIndex];
    
    // 解析变换
    float rotation = inst.transform.x;
    vec2 anchor = inst.transform.yz;
    float inst_scale = max(inst.transform.w, 1.0);
    
    // 计算顶点位置
    int quad_vertex = QUAD_INDICES[gl_VertexIndex];
    vec2 local_pos = QUAD_POSITIONS[quad_vertex];
    
    // 应用变换
    vec2 centered = local_pos - anchor;
    centered *= inst_scale;
    centered = rotate2D(rotation) * centered;
    vec2 transformed = (centered + anchor) * inst.rect.zw + inst.rect.xy;
    
    // 输出
    v_uv = local_pos;
    v_start_color = inst.start_color;
    v_instance_id = gl_InstanceIndex;
    
    vec2 ndc = transformed * pc.scale + pc.translate;
    gl_Position = vec4(ndc, 0.0, 1.0);
}

5. 错误处理策略

5.1 双层错误检测

采用 工具编译时报错 + C++ 静态检查 的双层策略:

┌─────────────────────────────────────────────────────────────────────────────┐
│ 第一层Python 工具编译时报错(主要)                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│ 时机:着色器编译阶段                                                         │
│ 优点:                                                                       │
│   - 错误发现最早                                                             │
│   - 错误信息可以精确指向着色器源码位置                                       │
│   - 阻止生成错误的代码                                                       │
│ 检查内容:                                                                   │
│   - @instance_buffer 标记存在性                                              │
│   - rect 字段存在、类型、偏移                                                │
│   - 结构体 16 字节对齐                                                       │
│   - Runtime Array 存在                                                       │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ 第二层C++ 静态检查(防御性)                                               │
├─────────────────────────────────────────────────────────────────────────────┤
│ 时机C++ 项目编译阶段                                                       │
│ 优点:                                                                       │
│   - 二次验证,防止工具 bug                                                   │
│   - 验证 C++ 类型与 SPIR-V 布局一致                                          │
│   - 捕获手动修改生成文件导致的问题                                           │
│ 检查内容:                                                                   │
│   - sizeof 和 alignof 验证                                                   │
│   - offsetof 验证关键字段偏移                                                │
└─────────────────────────────────────────────────────────────────────────────┘

5.2 工具编译时错误示例

# spirv_parser.py 中的验证逻辑

def validate_instance_buffer(buffer: BufferInfo, reflection: SPIRVReflection) -> None:
    """验证实例缓冲布局
    
    Raises:
        ToolError: 如果验证失败
    """
    struct_type = buffer.struct_type
    
    # 检查 1必须有成员
    if not struct_type.members:
        raise ToolError(
            f"Instance buffer '{buffer.name}' struct has no members. "
            f"Expected at least 'rect' field."
        )
    
    # 检查 2第一个成员必须是 rect
    first_member = struct_type.members[0]
    if first_member.name != "rect":
        raise ToolError(
            f"Instance buffer '{buffer.name}': first member must be named 'rect', "
            f"got '{first_member.name}'. The rect field is required for positioning."
        )
    
    # 检查 3rect 必须是 vec4
    rect_type = first_member.resolved_type
    if not is_vec4_float(rect_type, reflection.types):
        raise ToolError(
            f"Instance buffer '{buffer.name}': 'rect' must be vec4 (16 bytes), "
            f"got {spirv_type_to_cpp(rect_type, reflection.types)}."
        )
    
    # 检查 4rect 偏移必须为 0
    if first_member.offset != 0:
        raise ToolError(
            f"Instance buffer '{buffer.name}': 'rect' offset must be 0, "
            f"got {first_member.offset}."
        )
    
    # 检查 5结构体大小必须是 16 的倍数
    total_size = calculate_std430_size(struct_type, reflection.types)
    if total_size % 16 != 0:
        raise ToolError(
            f"Instance buffer '{buffer.name}': struct size must be multiple of 16, "
            f"got {total_size} bytes. Add padding to align."
        )

错误输出示例

ERROR: Instance buffer 'InstanceBuffer': first member must be named 'rect', got 'position'.
       The rect field is required for positioning.
       
       In: shaders/widget/gradient_instanced.vert.glsl
       At: layout(std430, set = 0, binding = 0) readonly buffer InstanceBuffer

5.3 生成代码中的静态检查

// 生成的 C++ 代码

struct alignas(16) gradient_instance_data {
    Eigen::Vector4f rect;
    Eigen::Vector4f start_color;
    Eigen::Vector4f transform;
    Eigen::Vector4f custom_params;
};

// ============ 编译期静态验证 ============

// 验证总大小
static_assert(
    sizeof(gradient_instance_data) == 64,
    "gradient_instance_data: size mismatch with shader layout (expected 64 bytes)"
);

// 验证 16 字节对齐
static_assert(
    sizeof(gradient_instance_data) % 16 == 0,
    "gradient_instance_data: must be 16-byte aligned for GPU compatibility"
);

// 验证 rect 字段偏移
static_assert(
    offsetof(gradient_instance_data, rect) == 0,
    "gradient_instance_data: rect must be at offset 0"
);

// 验证对齐要求
static_assert(
    alignof(gradient_instance_data) >= 16,
    "gradient_instance_data: alignment must be at least 16"
);

5.4 为什么需要双层检测

场景 工具层捕获 C++ 静态检查捕获
着色器缺少 rect 字段
着色器 rect 类型错误
工具生成错误的 C++ 代码
手动修改生成文件
C++ 编译器 padding 与预期不同

6. 实现步骤清单

Phase 1: SPIR-V 解析扩展

步骤 任务 文件
1.1 添加 BUILTIN_INSTANCE_INDEX 常量 tools/constants.py
1.2 实现源码注释解析逻辑(提取 @instance_buffer tools/compiler.py
1.3 实现 detect_instance_buffer() 函数 tools/spirv_parser.py
1.4 实现 validate_instance_buffer() 函数 tools/spirv_parser.py
1.5 扩展 SPIRVReflection 添加 instance_buffer 字段 tools/types.py

Phase 2: 代码生成扩展

步骤 任务 文件
2.1 创建 generate_instance_data_struct() 函数 tools/code_generator.py
2.2 创建 instance_data.jinja2 模板(含静态检查) tools/templates/instance/
2.3 更新 header.jinja2 集成实例数据 tools/templates/base/header.jinja2
2.4 更新 shader_spec 添加 instance_typesupports_instancing tools/templates/base/header.jinja2

Phase 3: 类型映射完善

步骤 任务 文件
3.1 确保 rect 字段正确对齐 tools/type_mapping.py
3.2 添加 16 字节边界对齐填充计算 tools/type_mapping.py

Phase 4: 集成测试

步骤 任务 文件
4.1 创建测试着色器 test_instanced.vert.glsl shaders/widget/
4.2 验证生成的 C++ 代码 测试脚本
4.3 测试错误情况(缺少 rect、类型错误等 测试脚本

7. 与现有 shader_spec 的集成

7.1 扩展后的 shader_spec

// 生成的代码示例
// build/generated/shaders/gradient_instanced_vert_frag.h

namespace mirage::shaders {

// ============ 实例数据结构 ============
// 从 @instance_buffer 标记的 SSBO 自动生成

struct alignas(16) gradient_instance_data {
    // 必需字段(工具验证)
    Eigen::Vector4f rect;         // x, y, width, height
    
    // 用户定义字段(从着色器反射)
    Eigen::Vector4f start_color;  // 起始颜色
    Eigen::Vector4f transform;    // rotation, anchor_x, anchor_y, scale
    Eigen::Vector4f custom_params;
};

// ============ 编译期静态验证 ============

static_assert(sizeof(gradient_instance_data) == 64);
static_assert(sizeof(gradient_instance_data) % 16 == 0);
static_assert(offsetof(gradient_instance_data, rect) == 0);

// ============ 共享参数结构 ============
// 从 UBO GradientSharedParams 生成

struct alignas(16) GradientSharedParams {
    Eigen::Vector4f end_color;
    float gradient_type;
    float gradient_angle;
    Eigen::Vector2f _padding;
};

// ============ 着色器规格 ============

template <typename ParamsT = GradientSharedParams>
struct gradient_instanced_shader_spec {
    using params_type = ParamsT;
    using instance_type = gradient_instance_data;
    
    /// @brief 是否支持实例化渲染
    static constexpr bool supports_instancing = true;
    
    static constexpr std::string_view name = "gradient_instanced";
    static constexpr uint32_t shader_id = /* hash */;
    
    [[nodiscard]] static constexpr auto get_vertex_spirv() noexcept
        -> std::span<const uint32_t> {
        return gradient_instanced_vert_spirv;
    }
    
    [[nodiscard]] static constexpr auto get_fragment_spirv() noexcept
        -> std::span<const uint32_t> {
        return gradient_instanced_frag_spirv;
    }
    
    [[nodiscard]] static constexpr auto get_bindings() noexcept
        -> std::span<const shader_binding_info> {
        return gradient_instanced_bindings;
    }
    
    /// @brief 获取实例数据大小
    [[nodiscard]] static constexpr size_t get_instance_size() noexcept {
        return sizeof(instance_type);
    }
};

} // namespace mirage::shaders

7.2 用户使用方式

// 用户代码(完全自动化)
#include "generated/shaders/gradient_instanced_vert_frag.h"

using namespace mirage::shaders;

// 直接使用生成的实例数据类型
std::vector<gradient_instance_data> instances;
instances.push_back({
    .rect = {100.0f, 100.0f, 200.0f, 150.0f},
    .start_color = {1.0f, 0.0f, 0.0f, 1.0f},
    .transform = {0.0f, 0.5f, 0.5f, 1.0f},  // 无旋转,中心锚点
    .custom_params = {0.0f, 0.0f, 0.0f, 0.0f}
});

// 框架自动处理合批

8. 总结

8.1 设计要点

  1. 识别规则:通过 @instance_buffer 注释显式标记
  2. 验证策略:工具编译时报错(主要)+ C++ 静态检查(防御性)
  3. 必需字段rect (vec4) 作为第一个成员,偏移为 0
  4. 对齐要求:结构体大小必须是 16 字节的倍数

8.2 修改文件清单

文件 修改类型 描述
tools/constants.py 新增 BUILTIN_INSTANCE_INDEX 常量
tools/compiler.py 修改 添加注释解析逻辑
tools/spirv_parser.py 修改 添加实例缓冲检测和验证
tools/types.py 修改 扩展 SPIRVReflection
tools/code_generator.py 修改 添加实例数据结构生成
tools/templates/base/header.jinja2 修改 集成实例数据到输出
tools/templates/instance/instance_data.jinja2 新增 实例数据结构模板

8.3 后续工作

本设计方案完成后,需要与 instanced_rendering_api_design.md 中的渲染器实现配合,实现完整的实例化渲染管线。