VTK开发精要
济南友泉软件有限公司
图形图像处理与科学计算数据可视化的数据主要由几何信息、拓扑信息、属性信息等三类数据组成。VTK定义一套数据结构用于存储这三类信息。
vtkDataArray派生于vtkAbstractArray,实际上一个Tuple元素的动态数组,每个Tuple包含vtkAbstractArray::NumberOfComponents个分量,总共包含vtkAbstractArray::MaxId个Tuples。
vtkFiledData实际上是一组vtkDataArray (A group of vtkDataArray objects)。不同vtkDataArray可以有不同的Tuple数目,而且不同vtkDataArray中Tuple的分量大小也可以不同。可以看到,可以将速度、压力、温度等场变量存储到vtkFiledData内部的vtkDataArray中。
另外,从vtkFieldData派生的vtkPointData与vtkCellData分别用于存储定义在节点、单元中心的多个场数据(速度、压力、温度等)。
vtkDataObject包含一个vtkFieldData类型的成员变量FieldData,同时vtkDataObject定义了数据可视化过程中通过vtkInformation、vtkInformationVector等信息对象来控制成员变量vtkDataObject::FieldData的接口。
VTK中定义了大量的派生于vtkDataObject的类,参见附录A。
vtkDataSet直接派生于vtkDataObject,在vtkDataObject基础之上,其内部增加了vtkPointData、vtkCellData用于存储定义在节点、单元中心的场数据。vtkDataSet关联了几何、拓扑与属性信息,可以方便地进行可视化。
vtkMultiBlockDataSet间接派生于vtkDataObject,提供类树状结构,每个树节点包含一个vtkDataObject指针。在CFD中,可以将计算结果按照空间或者时间存储到vtkMultiBlockDataSet的不同blocks内。
处理vtkMultiBlockDataSet这种混合数据的基本流程就是将树状结构中的每一个vtkDataObject进行处理,然后将所有的处理结果进行合并。
VTK使用管线机制来完成数据与信息的处理,管线是由多个Filter顺序组合而成的逻辑上的管线状的结构。目前,VTK提供了vtkDemandDrivenPipeline、vtkStreamingDemandPipeline、vtkCompositeDataPipeline等三种管线实现(附录B)。
VTK管线用于将多个Filter组合在一起以处理数据与信息,构成了逻辑上“线状”的结构。
Filter指的是派生于vtkAlgorithm的类,用于处理数据与算法。Filter由输入端口、算法、执行对象(vtkExcutive及其子类)、输出端口、信息对象等组成。
每个输入端口可以包含多个连接,而每个输出端口只有一个连接。当Filter输入端口个数为0,这样的Filter也被成为Reader或者Source;当Filter的输出端口个数为0,这样的Filter被称作Writer或者Sink。
Filter内部的算法通常是在重写vtkAlgorithm::ProcessRequest()函数,或者派生于vtkAlgorithm子类,然后重写对应的请求处理函数。例如可以派生vtkMultiBlockAlgorithm,然后通过重写RequestData()函数响应REQUEST_DATA_OBJECT请求。
每个Filter内部都有一个执行对象(vtkExcutive及其子类),用于控制管线的运行。当每次调用Filter的Update()函数更新数据时,实际上是通过执行对象生成多个请求,按照指定流向与顺序,在上游或者下游的Filter及其对应的执行对象内依次执行。
信息对象vtkInformation实际上VTK实现的一个Map数据结构,而vtkInfromationVector是一个vtkInformation类型数组。vtkInformation /vtkInfromationVector用于存储管线相关的信息,包括输入输出端口参数、算法数据等。
VTK中的管线机制的实现比较复杂,大体上主要涉及vtkExcutive及其子类、vtkAlgorithm、vtkInformation等相关类型。本节对vtkExcutive的实现做分析。
vtkExecutive内定义了vtkAlgorithm指针,用于关联对应的vtkAlgorithm对象,
// The algorithm managed by this executive.
vtkAlgorithm* Algorithm;
在vtkAlgorithm::SetExecutive函数中,完成vtkAlgorithm与vtkExecutive的关联,
void vtkAlgorithm::SetExecutive(vtkExecutive* newExecutive)
{
vtkExecutive* oldExecutive = this->Executive;
if(newExecutive != oldExecutive)
{
if(newExecutive)
{
newExecutive->Register(this);
vtkAlgorithmToExecutiveFriendship::SetAlgorithm(newExecutive, this);
}
this->Executive = newExecutive;
if(oldExecutive)
{
vtkAlgorithmToExecutiveFriendship::SetAlgorithm(oldExecutive, nullptr);
oldExecutive->UnRegister(this);
}
}
}
可以看到,vtkAlgorithm是通过vtkAlgorithmToExecutiveFriendship::SetAlgortihtm来调用vtkExecutive::SetAlgorithm设置。
class vtkAlgorithmToExecutiveFriendship
{
public:
static void SetAlgorithm(vtkExecutive* executive, vtkAlgorithm* algorithm)
{
executive->SetAlgorithm(algorithm);
}
};
vtkExecutive内定义了类型为vtkInformationVector的OutputInformation来存储每个输出端口的信息,
// Store an information object for each output port of the algorithm.
vtkInformationVector* OutputInformation;
vtkInformationVector是vtkInformation的数组,OutputInformation的每个vtkInformation元素对应输出端口的信息。
由于每个输入端口可以有多个连接,vtkExcutive定义了vtkExecutiveInternals类型的ExecutiveInternal来存储输入端口的信息,
// Internal implementation details.
vtkExecutiveInternals* ExecutiveInternal;
vtkExecutiveInternals实际上一个vtkInfromationVector的列表,可以看成一个vtkInformation的不等列表格,每一行表示一个输入端口,对应的列数等于端口上连接数目。
class vtkExecutiveInternals
{
public:
std::vector<vtkInformationVector*> InputInformation;
vtkExecutiveInternals();
~vtkExecutiveInternals();
vtkInformationVector** GetInputInformation(int newNumberOfPorts);
};
请求会通过ProcessRequest()下发,在这里,ProcessRequest()实际上起到分发请求的作用,即将请求交给不同的响应代码进行处理。
在vtkExcutive::ProcessRequest函数中,会根据FORWARD_DIRECTION设置的选项决定请求是向上(vtkExcutive::RequestUpstream)还是向下(vtkExcutive::RequestDownstream)传递,可以看到如果指定向上传递请求,则会调用ForwardUpstream这个虚函数将请求向上游传递。
vtkExecutive::ProcessRequest函数会根据ALGOTIYHM_BEFOR_FORWARD与ALGORITHM_AFTER_FORWARD来决定本Filter内部算法执行数据处理是发生在传递请求之前还是传递请求之后。
int vtkExecutive::ProcessRequest(vtkInformation* request,
vtkInformationVector** inInfo,
vtkInformationVector* outInfo)
{
if(request->Has(FORWARD_DIRECTION()))
{
// Request will be forwarded.
if(request->Get(FORWARD_DIRECTION()) == vtkExecutive::RequestUpstream)
{
if(this->Algorithm && request->Get(ALGORITHM_BEFORE_FORWARD()))
{
if(!this->CallAlgorithm(request, vtkExecutive::RequestUpstream,
inInfo, outInfo))
{
return 0;
}
}
if(!this->ForwardUpstream(request))
{
return 0;
}
if(this->Algorithm && request->Get(ALGORITHM_AFTER_FORWARD()))
{
if(!this->CallAlgorithm(request, vtkExecutive::RequestDownstream,
inInfo, outInfo))
{
return 0;
}
}
}
if(request->Get(FORWARD_DIRECTION()) == vtkExecutive::RequestDownstream)
{
vtkErrorMacro("Downstream forwarding not yet implemented.");
return 0;
}
}
else
{
// Request will not be forwarded.
vtkErrorMacro("Non-forwarded requests are not yet implemented.");
return 0;
}
return 1;
}
在ProcessRequest中,会调用vtkExcutive::CallAlgorithm来执行本Filter的数据处理。
int vtkExecutive::CallAlgorithm(vtkInformation* request, int direction,
vtkInformationVector** inInfo,
vtkInformationVector* outInfo)
{
// Copy default information in the direction of information flow.
this->CopyDefaultInformation(request, direction, inInfo, outInfo);
// Invoke the request on the algorithm.
this->InAlgorithm = 1;
int result = this->Algorithm->ProcessRequest(request, inInfo, outInfo);
this->InAlgorithm = 0;
// If the algorithm failed report it now.
if(!result)
{
vtkErrorMacro("Algorithm " << this->Algorithm->GetClassName()
<< "(" << this->Algorithm
<< ") returned failure for request: "
<< *request);
}
return result;
}
可以看到vtkExecutive会根据数据流的方向,调用CopyDefaultInformation将数据流从输出端口(输入连接)复制到输入连接(输出端口),然后调用虚函数vtkAlgorithm::ProcessRequest完成本Filter内实际的数据处理。
vtkDemandDrivenPipeline在vtkExcutive基础之上,增加请求触发功能,同时增加对REUQEST_DATA_OBJECT、REQUEST_INFROMATION、REUQEST_DATA等请求的支持。
如果管线采用vtkCompositeDataPipeline,则当调用vtkAlgorithm::Update函数之后,会调用vtkDemandDrivenPipeline::Update函数,其内部则是调用了两个虚函数UpdateInformation与UpdateData来完成主要业务。
int vtkDemandDrivenPipeline::Update(int port)
{
if(!this->UpdateInformation())
{
return 0;
}
if(port >= -1 && port < this->Algorithm->GetNumberOfOutputPorts())
{
return this->UpdateData(port);
}
else
{
return 1;
}
}
在vktCompositeDataPipeline::UpdateInformation中,首先调用UpdateDataObject虚函数生成REQUEST_DATA_OBJECT请求,并下发到ProcessRequest中
int vtkDemandDrivenPipeline::UpdateDataObject()
{
// The algorithm should not invoke anything on the executive.
if(!this->CheckAlgorithm("UpdateDataObject", nullptr))
{
return 0;
}
// Update the pipeline mtime first.
if(!this->UpdatePipelineMTime())
{
return 0;
}
// Setup the request for data object creation.
if (!this->DataObjectRequest)
{
this->DataObjectRequest = vtkInformation::New();
this->DataObjectRequest->Set(REQUEST_DATA_OBJECT());
// The request is forwarded upstream through the pipeline.
this->DataObjectRequest->Set(vtkExecutive::FORWARD_DIRECTION(), vtkExecutive::RequestUpstream);
// Algorithms process this request after it is forwarded.
this->DataObjectRequest->Set(vtkExecutive::ALGORITHM_AFTER_FORWARD(), 1);
}
// Send the request.
return this->ProcessRequest(this->DataObjectRequest,
this->GetInputInformation(),
this->GetOutputInformation());
}
在vktCompositeDataPipeline::UpdateInformation中,处理完REQUEST_DATA_OBJECT请求之后,会进一步创建REQUEST_INFORMATION请求,并将其下发到ProcessRequest处理。
int vtkDemandDrivenPipeline::UpdateInformation()
{
// The algorithm should not invoke anything on the executive.
if(!this->CheckAlgorithm("UpdateInformation", nullptr))
{
return 0;
}
// Do the data-object creation pass before the information pass.
if(!this->UpdateDataObject())
{
return 0;
}
// Setup the request for information.
if (!this->InfoRequest)
{
this->InfoRequest = vtkInformation::New();
this->InfoRequest->Set(REQUEST_INFORMATION());
// The request is forwarded upstream through the pipeline.
this->InfoRequest->Set(vtkExecutive::FORWARD_DIRECTION(), vtkExecutive::RequestUpstream);
// Algorithms process this request after it is forwarded.
this->InfoRequest->Set(vtkExecutive::ALGORITHM_AFTER_FORWARD(), 1);
}
// Send the request.
return this->ProcessRequest(this->InfoRequest,
this->GetInputInformation(),
this->GetOutputInformation());
}
处理完毕REQUEST_INFROMATION请求之后,vtkDemandDrivenPipeline::Update函数调用UpdateData虚函数,在vtkDemandDrivenPipeline::UpdateData中会创建REQUEST_DATA请求,并下发到ProcessRequest去处理。
int vtkDemandDrivenPipeline::UpdateData(int outputPort)
{
// The algorithm should not invoke anything on the executive.
if(!this->CheckAlgorithm("UpdateData", nullptr))
{
return 0;
}
// Range check.
if(outputPort < -1 ||
outputPort >= this->Algorithm->GetNumberOfOutputPorts())
{
vtkErrorMacro("UpdateData given output port index "
<< outputPort << " on an algorithm with "
<< this->Algorithm->GetNumberOfOutputPorts()
<< " output ports.");
return 0;
}
// Setup the request for data.
if (!this->DataRequest)
{
this->DataRequest = vtkInformation::New();
this->DataRequest->Set(REQUEST_DATA());
// The request is forwarded upstream through the pipeline.
this->DataRequest->Set(vtkExecutive::FORWARD_DIRECTION(), vtkExecutive::RequestUpstream);
// Algorithms process this request after it is forwarded.
this->DataRequest->Set(vtkExecutive::ALGORITHM_AFTER_FORWARD(), 1);
}
// Send the request.
this->DataRequest->Set(FROM_OUTPUT_PORT(), outputPort);
return this->ProcessRequest(this->DataRequest,
this->GetInputInformation(),
this->GetOutputInformation());
}
vtkStreamingDemandDrivenPipeline在vtkDemandDrivenPipeline提供的流水线功能基础之上,主要是增加对局部数据(分片数据)更新的功能。这一部分比较复杂,暂时不予以研究。
vktCompositeDataPipeline不仅可以支持非混合数据,同时提供了对等混合数据处理算法的支持,混合数据算法指的是能够处理vtkMultiBlockDataSet、vtkUniformGridAMR等数据的vtkAlgorithm派生类。
vktCompositeDataPipeline是最新VTK默认使用的管线。(笔者机器上使用的VTK版本是VTK-8.2.0)