Skip to content

VTK管线Executive与Information传递机制深剖

在 VTK 中,数据处理被抽象为由“数据源(Source)”、“过滤器(Filter)”和“映射器(Mapper)”通过管道连接而成的数据管线(Pipeline)

开发者在调用 filter->Update()renWin->Render() 时,VTK 底层究竟发生了什么?为什么参数没变时调用 Update() 瞬间就能返回?元数据(如 ROI 裁剪框、时间序列)是如何在管线中传递的?

这一切的核心都归功于 VTK 的 Executive(执行器)与 vtkInformation 键值对传递机制


一、VTK 管线的双层解构架构

VTK 巧地将“算法业务”与“管线调度控制”进行了解耦:

  • vtkAlgorithm(算法层):只关心具体的算法逻辑(如 RequestData() 里如何将点云三角化),不关心数据何时被请求。
  • vtkExecutive(执行器层):决定何时执行算法、检查输入输出是否过期、传递元数据参数。

二、vtkInformation 键值对元数据系统

在管线计算前,各个节点之间需要交换元数据(如“该数据集包含 500 张 CT 切片”、“当前请求第 12 帧”),而不能提前载入整个 GB 级别的超大对象。

vtkInformation 担当了轻量级元数据字典的角色,它是一个异构键值对容器:

cpp
#include <vtkInformation.h>
#include <vtkInformationIntegerKey.h>

// VTK 内部定义的公共 Keys
vtkInformation* info = ...;
info->Set(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER(), 0);
info->Set(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES(), 4);

三、管线更新的两阶段 Pass 流程

当用户调用 outputFilter->Update() 时,vtkExecutive 会启动一个双向 Pass 更新流程

3.1 阶段一:RequestInformation Pass

自底向上逐级询问:上游数据源一共有多少数据?数据范围是多少?(不占用堆内存生成真实点集)。

3.2 阶段二:RequestData Pass

检查输入输出对象的 MTime(修改时间戳)。只有当 Input.MTime > Output.MTime 或者 Filter 自身的参数被调用过 Modified() 时,才会真正触发 RequestData() 执行耗时计算(按需懒加载与增量更新)。


四、自定义 vtkAlgorithm 子类实现规范

下面演示如何编写一个标准的自定义 Filter 类,规范实现 RequestInformationRequestData

cpp
#include <vtkPolyDataAlgorithm.h>
#include <vtkInformation.h>
#include <vtkInformationVector.h>
#include <vtkPolyData.h>
#include <vtkObjectFactory.h>

class MyCustomFilter : public vtkPolyDataAlgorithm {
public:
    static MyCustomFilter* New();
    vtkTypeMacro(MyCustomFilter, vtkPolyDataAlgorithm);

protected:
    MyCustomFilter() {
        this->SetNumberOfInputPorts(1);  // 1 个输入端口
        this->SetNumberOfOutputPorts(1); // 1 个输出端口
    }

    // 1. 元信息请求阶段 (轻量级)
    int RequestInformation(vtkInformation* request,
                           vtkInformationVector** inputVector,
                           vtkInformationVector* outputVector) override {
        // 获取输出元数据字典
        vtkInformation* outInfo = outputVector->GetInformationObject(0);
        
        // 设置自定义元数据 (例如声明输出数据的包围盒)
        double bounds[6] = {-10.0, 10.0, -10.0, 10.0, -10.0, 10.0};
        outInfo->Set(vtkStreamingDemandDrivenPipeline::BOUNDS(), bounds, 6);

        return 1; // 返回 1 表示成功
    }

    // 2. 真实数据生成阶段 (重型计算)
    int RequestData(vtkInformation* request,
                    vtkInformationVector** inputVector,
                    vtkInformationVector* outputVector) override {
        // 获取输入与输出数据对象
        vtkPolyData* input = vtkPolyData::GetData(inputVector[0], 0);
        vtkPolyData* output = vtkPolyData::GetData(outputVector, 0);

        if (!input || input->GetNumberOfPoints() == 0) {
            return 0;
        }

        // 执行算法计算逻辑 (将输入浅拷贝或加工后赋予输出)
        output->ShallowCopy(input);

        return 1;
    }
};

vtkStandardNewMacro(MyCustomFilter);

五、总结

VTK 管线高效率的秘密可以概括为两句话:

  1. 元数据先行:在 RequestInformation 阶段用轻量的 vtkInformation 确定数据规格,避免无效内存分配。
  2. 时间戳防重算:在 RequestData 阶段校验 MTime,只有数据确实被修改(Modified())时才重新计算。

基于 VitePress 强力驱动 | 记录技术与生活