12.惰性更新机制
Update / Modified / MTime 管线惰性更新机制
VTK流水线是惰性求值:不会改一点就立刻计算,等到真正需要结果的时候才去执行。 核心三要素:
Modified()、MTime、Update(),结合上一节讲的观察者ModifiedEvent一起工作。
1. MTime(ModifiedTime) 修改时间
每一个vtkObject内部,都维护一个MTime(unsigned long 整型)。
- 对象创建出来,MTime有一个初始值。
- 只要调用
obj->Modified(),MTime就会自动+1。 MTime本质就是一个版本号,用来标记:这个对象的数据有没有发生改动。
auto src = vtkSmartPointer<vtkCylinderSource>::New();
cout << src->GetMTime() << endl;
src->SetRadius(5.0); // 修改参数,内部自动调用 Modified()
cout << src->GetMTime() << endl; // MTime 数值变大
只要你修改VTK对象的属性(SetXXX函数),绝大多数Set接口内部都会自动调用
Modified(),MTime自动增长。
⚠️注意:直接修改PolyData内部点数组,没有走Set接口,不会自动调用Modified(),必须手动写
polyData->Modified()。
2. Modified()做了两件事
- 对象自身MTime版本号+1,标记本对象数据过期;
- 触发
ModifiedEvent事件,所有注册在这个对象上的观察者全部收到通知(上一节观察者模式)。
Modified()不会执行任何算法计算,不会生成数据,仅仅打标记+发事件通知。
3. Update():真正执行流水线计算
Update() 的工作逻辑: 从当前Filter出发,沿着管线向上回溯,对比上下游MTime。
- 如果上游组件MTime > 下游记录的上次执行时间 → 上游数据变了,需要重新计算;
- 递归向上,把上游全部需要更新的Filter全部执行一遍;
- 计算完成,保存本次的MTime,标记输出为最新。
示例管线:CylinderSource → vtkCleanPolyData → Mapper
source(圆柱体源) → cleanFilter(去重点) → mapper
- 修改source半径,
source->Modified(),MTime变大; - cleanFilter发现输入source的MTime比自己上次计算记录的时间更大,判定输入过期;
- 调用
cleanFilter->Update(),向上驱动source执行,再执行cleanFilter算法,输出新数据。
很多新手误区:
cleanFilter->SetInputData(polyData); //忘记写Update,直接拿输出 auto out = cleanFilter->GetOutput();此时Filter根本没有运行,out拿到的是旧的空数据。
4. 两种输入连接方式的区别(高频面试点)
① SetInputConnection(端口连接,管线完整联动)
filterB->SetInputConnection(filterA->GetOutputPort());
建立端口连接,内部会监听上游的ModifiedEvent观察者事件。 上游A调用Modified,B就收到事件,标记自己输出过期。 调用B的Update,整条管线自动向上跑。✅推荐管线串联使用。
② SetInputData(直接传入数据指针,共享内存,不建立观察者监听)
filterB->SetInputData(polyData);
只是记录原始数据指针,不会建立观察者监听。 polyData调用Modified(),filterB收不到事件通知,filterB不知道输入已经改变。
所以修改polyData之后,你必须手动调用
filterB->Update(),否则filterB依旧使用旧数据。
重点区分:
- SetInputConnection:带观察者事件联动,上游修改自动通知下游;
- SetInputData:只是裸指针传入,没有观察者联动,需要手动Update。
5. Mapper什么时候触发Update?
Mapper在渲染的时候,Render()内部会自动调用管线Update。
m_renderer->GetRenderWindow()->Render();
渲染这一步,mapper会自动向上触发整条管线更新。 👉所以很多业务代码不需要手动到处写xxx->Update()。
错误示例:多余Update
cleanFilter->Update(); //第一次执行
mapper->SetInputConnection(cleanFilter->GetOutputPort());
cleanFilter->Update(); //多余!渲染时mapper还会再触发一次管线
大量无意义Update,大模型会严重浪费CPU。
6. 经典踩坑场景
坑1:直接修改PolyData内部数组,画面不刷新
vtkPoints* pts = polyData->GetPoints();
pts->SetPoint(0, 10,0,0);
//忘记 pts->Modified(); polyData->Modified();
renderWindow->Render();
直接修改内部数组,不会自动调用Modified,MTime不变,管线认为数据没变,画面纹丝不动。 修复:修改数组后手动调用
pts->Modified(); polyData->Modified();,再Render。
坑2:SetInputData,修改输入数据,下游Filter没有感知
filter->SetInputData(polyData);
//修改polyData
polyData->Modified();
//filter没有收到ModifiedEvent,不知道输入变了!
filter->Update(); //必须手动调用Update
坑3:MouseMoveEvent回调里面无脑Update
鼠标每移动一次就Update一次重型Filter,大模型直接卡死。
原则:只有数据真正发生变化,才执行Update;不要在高频事件循环中反复跑Filter。
7. 完整流程串讲(把观察者+MTime+Update打通)
管线:Source → Filter → Mapper
- 修改Source参数,
source->SetXXX(),内部执行source->Modified(); - source MTime版本号+1;触发
ModifiedEvent; - 通过
SetInputConnection连接的Filter作为观察者收到事件,标记自身输出过期; - 窗口调用Render();
- Mapper发现上游已经过期,自动向上触发
Update(); - 依次执行source、filter算法,生成新数据,渲染出新画面。
整条链路:观察者通知状态变化 → MTime标记版本过期 → Update惰性执行计算。
8. 调试小技巧
//打印对象修改时间,调试管线是否真的更新
std::cout<<"source MTime:"<<source->GetMTime()<<std::endl;
std::cout<<"filter MTime:"<<filter->GetMTime()<<std::endl;
对比MTime,就能判断:对象到底有没有被标记为修改。
练习任务
- 使用
SetInputData方式给filter设置polyData,修改polyData点,忘记Modified,观察画面不会更新;补上Modified之后,调用Update,观察结果。 - 换成
SetInputConnection端口连接,修改上游source,观察管线自动联动。

浙公网安备 33010602011771号