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()做了两件事

  1. 对象自身MTime版本号+1,标记本对象数据过期;
  2. 触发 ModifiedEvent 事件,所有注册在这个对象上的观察者全部收到通知(上一节观察者模式)。

Modified()不会执行任何算法计算,不会生成数据,仅仅打标记+发事件通知。

3. Update():真正执行流水线计算

Update() 的工作逻辑: 从当前Filter出发,沿着管线向上回溯,对比上下游MTime。

  • 如果上游组件MTime > 下游记录的上次执行时间 → 上游数据变了,需要重新计算;
  • 递归向上,把上游全部需要更新的Filter全部执行一遍;
  • 计算完成,保存本次的MTime,标记输出为最新。

示例管线:CylinderSource → vtkCleanPolyData → Mapper

source(圆柱体源) → cleanFilter(去重点) → mapper
  1. 修改source半径,source->Modified(),MTime变大;
  2. cleanFilter发现输入source的MTime比自己上次计算记录的时间更大,判定输入过期;
  3. 调用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

  1. 修改Source参数,source->SetXXX(),内部执行source->Modified();
  2. source MTime版本号+1;触发ModifiedEvent;
  3. 通过SetInputConnection连接的Filter作为观察者收到事件,标记自身输出过期;
  4. 窗口调用Render();
  5. Mapper发现上游已经过期,自动向上触发Update();
  6. 依次执行source、filter算法,生成新数据,渲染出新画面。

整条链路:观察者通知状态变化 → MTime标记版本过期 → Update惰性执行计算。

8. 调试小技巧

//打印对象修改时间,调试管线是否真的更新
std::cout<<"source MTime:"<<source->GetMTime()<<std::endl;
std::cout<<"filter MTime:"<<filter->GetMTime()<<std::endl;

对比MTime,就能判断:对象到底有没有被标记为修改。

练习任务

  1. 使用SetInputData方式给filter设置polyData,修改polyData点,忘记Modified,观察画面不会更新;补上Modified之后,调用Update,观察结果。
  2. 换成SetInputConnection端口连接,修改上游source,观察管线自动联动。
posted @ 2026-08-24 16:11  Drunker•L  阅读(17)  评论(0)    收藏  举报