VTK 渲染全流程与 OpenGL 上下文适配

VTK 渲染全流程与 OpenGL 上下文适配

基于 VTK v9.5.0 源码(D:\vtk)整理,覆盖两个主题:

  1. 上下文适配原理 ------ QVTKOpenGLNativeWidget 如何通过 vtkGenericOpenGLRenderWindow 借用 Qt 创建的 OpenGL 上下文;
  2. 渲染全流程 ------ 从对象创建、上下文初始化、一帧渲染,到退出清理的完整调用链。

核心结论:Qt 负责创建并持有 OpenGL 上下文,VTK 通过事件回调"借用"这个上下文来渲染;整个过程发生在同一个进程内。


0. 全流程时序图

时序图分布在各自章节:

  • 初始化 ------ 见 §7.4 初始化时序图
  • 多帧/交互 ------ 见 §8.9 多帧与交互时序图
  • 退出清理 ------ 见 §10.5 退出清理时序图

第一部分:上下文适配原理

1. 为什么必须用 vtkGenericOpenGLRenderWindow

QOpenGLWidget 会接管窗口管理和 OpenGL 上下文创建,而平台相关的 vtkWin32OpenGLRenderWindow / vtkXOpenGLRenderWindow 也想自己创建窗口和上下文,两者冲突。

因此 VTK 提供了"空壳"渲染窗口 vtkGenericOpenGLRenderWindow:

  • 继承自 vtkOpenGLRenderWindow。
  • 所有平台相关的窗口 / 上下文操作都被改写成发射 VTK 事件,而不是直接调用 WGL / GLX / EGL。

关键实现(vtkGenericOpenGLRenderWindow.cxx:79-100):

cpp 复制代码
void vtkGenericOpenGLRenderWindow::MakeCurrent()
{
  this->InvokeEvent(vtkCommand::WindowMakeCurrentEvent, nullptr);   // 只发事件,不做事
}

bool vtkGenericOpenGLRenderWindow::IsCurrent()
{
  this->InvokeEvent(vtkCommand::WindowIsCurrentEvent, &this->CurrentStatus); // 结果由回调填入
  return this->CurrentStatus;
}

WindowId / DisplayId / Context / Drawable 等 getter 全部返回 nullptr。真正干活的是"谁观察了这些事件"------即 Qt 侧的适配器。

2. 自管理上下文 vs 借用上下文:OwnContext 与上下文管理接口

vtkGenericOpenGLRenderWindow 之所以能"只发事件、不做任何 GL 调用",是因为它把 VTK 的上下文管理接口整体改写成了事件。理解这套接口,是理解下文"事件桥接"章节的前提。

2.1 上下文管理接口(虚函数)

最底层抽象定义在 vtkRenderWindow(Rendering/Core/vtkRenderWindow.h),默认都是桩实现:

虚函数 默认实现 语义
IsCurrent() return false 当前线程的 current 上下文是否为本窗口
SupportsOpenGL() return 0 是否支持 OpenGL
IsDirect() return 0 是否硬件加速
InitializeFromCurrentContext() return false 认领一个已经 current 的上下文

中间层 vtkOpenGLRenderWindow 补充 GL 状态管理与切换的通用逻辑,并把真正的窗口 / 上下文创建下沉为纯虚函数(Rendering/OpenGL2/vtkOpenGLRenderWindow.h):

cpp 复制代码
virtual void CreateAWindow() = 0;                   // 创建窗口 + 上下文
virtual void DestroyWindow() = 0;                   // 销毁
virtual void PushContext() { this->MakeCurrent(); } // 默认兜底:只 MakeCurrent
virtual void PopContext() {}

各平台子类实现 MakeCurrent / ReleaseCurrent / IsCurrent / CreateAWindow / DestroyWindow:vtkWin32OpenGLRenderWindow(WGL)、vtkXOpenGLRenderWindow(GLX)、vtkWebAssemblyOpenGLRenderWindow(WebGL2)。

2.2 OwnContext:自管理 vs 借用的分界

关键标志在 vtkOpenGLRenderWindow:

Flag telling if the context has been created here or was inherited.

  • 构造函数默认 OwnContext = 1 ------ VTK 自己创建、管理并销毁上下文("自管理")。
  • InitializeFromCurrentContext() 置为 0 ------ 上下文是"从当前上下文继承来的",VTK 只借用、不销毁。

渲染入口 vtkOpenGLRenderWindow::Start() 据此决定是否重置 GL 状态缓存:

cpp 复制代码
this->MakeCurrent();
if (!this->OwnContext)
  this->GetState()->Initialize(this); // 外部上下文状态不可信,每次 render 前重置

Qt 适配走的正是"借用"这一路 :QVTKRenderWindowAdapter 里 SetOwnContext(0)(QVTKRenderWindowAdapter.cxx:138),并由下文"状态管理"章节里的 ostate->Reset() 自己保证状态复位------与上面 !OwnContext 分支语义完全一致。

3. 三层架构

层 类 职责
Qt 窗口层 QVTKOpenGLNativeWidget(继承 QOpenGLWidget) 拥有 QOpenGLContext,提供 FBO,驱动 initializeGL / paintGL / resizeGL 生命周期
适配层 QVTKRenderWindowAdapter(内部 QObject) 同时持有 QOpenGLContext 和 vtkGenericOpenGLRenderWindow,在两者之间桥接
VTK 窗口层 vtkGenericOpenGLRenderWindow VTK 侧的渲染窗口,用事件替代平台 GL 调用

3.1 模块分层与对象持有关系(flowchart)

#mermaid-svg-TLFVM5dl8LsLKaxA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TLFVM5dl8LsLKaxA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TLFVM5dl8LsLKaxA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TLFVM5dl8LsLKaxA .error-icon{fill:#552222;}#mermaid-svg-TLFVM5dl8LsLKaxA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TLFVM5dl8LsLKaxA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TLFVM5dl8LsLKaxA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TLFVM5dl8LsLKaxA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TLFVM5dl8LsLKaxA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TLFVM5dl8LsLKaxA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TLFVM5dl8LsLKaxA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TLFVM5dl8LsLKaxA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TLFVM5dl8LsLKaxA .marker.cross{stroke:#333333;}#mermaid-svg-TLFVM5dl8LsLKaxA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TLFVM5dl8LsLKaxA p{margin:0;}#mermaid-svg-TLFVM5dl8LsLKaxA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TLFVM5dl8LsLKaxA .cluster-label text{fill:#333;}#mermaid-svg-TLFVM5dl8LsLKaxA .cluster-label span{color:#333;}#mermaid-svg-TLFVM5dl8LsLKaxA .cluster-label span p{background-color:transparent;}#mermaid-svg-TLFVM5dl8LsLKaxA .label text,#mermaid-svg-TLFVM5dl8LsLKaxA span{fill:#333;color:#333;}#mermaid-svg-TLFVM5dl8LsLKaxA .node rect,#mermaid-svg-TLFVM5dl8LsLKaxA .node circle,#mermaid-svg-TLFVM5dl8LsLKaxA .node ellipse,#mermaid-svg-TLFVM5dl8LsLKaxA .node polygon,#mermaid-svg-TLFVM5dl8LsLKaxA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TLFVM5dl8LsLKaxA .rough-node .label text,#mermaid-svg-TLFVM5dl8LsLKaxA .node .label text,#mermaid-svg-TLFVM5dl8LsLKaxA .image-shape .label,#mermaid-svg-TLFVM5dl8LsLKaxA .icon-shape .label{text-anchor:middle;}#mermaid-svg-TLFVM5dl8LsLKaxA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TLFVM5dl8LsLKaxA .rough-node .label,#mermaid-svg-TLFVM5dl8LsLKaxA .node .label,#mermaid-svg-TLFVM5dl8LsLKaxA .image-shape .label,#mermaid-svg-TLFVM5dl8LsLKaxA .icon-shape .label{text-align:center;}#mermaid-svg-TLFVM5dl8LsLKaxA .node.clickable{cursor:pointer;}#mermaid-svg-TLFVM5dl8LsLKaxA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TLFVM5dl8LsLKaxA .arrowheadPath{fill:#333333;}#mermaid-svg-TLFVM5dl8LsLKaxA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TLFVM5dl8LsLKaxA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TLFVM5dl8LsLKaxA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TLFVM5dl8LsLKaxA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TLFVM5dl8LsLKaxA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TLFVM5dl8LsLKaxA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TLFVM5dl8LsLKaxA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TLFVM5dl8LsLKaxA .cluster text{fill:#333;}#mermaid-svg-TLFVM5dl8LsLKaxA .cluster span{color:#333;}#mermaid-svg-TLFVM5dl8LsLKaxA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TLFVM5dl8LsLKaxA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TLFVM5dl8LsLKaxA rect.text{fill:none;stroke-width:0;}#mermaid-svg-TLFVM5dl8LsLKaxA .icon-shape,#mermaid-svg-TLFVM5dl8LsLKaxA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TLFVM5dl8LsLKaxA .icon-shape p,#mermaid-svg-TLFVM5dl8LsLKaxA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TLFVM5dl8LsLKaxA .icon-shape .label rect,#mermaid-svg-TLFVM5dl8LsLKaxA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TLFVM5dl8LsLKaxA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TLFVM5dl8LsLKaxA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TLFVM5dl8LsLKaxA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} vtkSmartPointer 持有
QScopedPointer 持有
继承 QOpenGLWidget 使用
QPointer 引用
vtkSmartPointer 持有
持有
AddObserver 观察
Renderers 集合
持有 Interactor
交互风格
转发事件
Props 集合
活动相机
持有 Mapper
持有 Property
输入数据
点
单元
缓存 VBO
索引 IBO
顶点属性
状态缓存
shader 缓存
VBO 缓存
编译/缓存
管理 FBO
QVTKOpenGLNativeWidget

(继承 QOpenGLWidget)
QOpenGLContext(由 Qt 创建)
QVTKRenderWindowAdapter
QVTKInteractorAdapter
QVTKInteractor
vtkInteractorStyleTrackballCamera
vtkGenericOpenGLRenderWindow

(vtkOpenGLRenderWindow)
vtkOpenGLRenderer
vtkOpenGLCamera
vtkOpenGLActor
vtkOpenGLPolyDataMapper
vtkProperty
vtkPolyData
vtkPoints
vtkCellArray
vtkOpenGLState
vtkOpenGLShaderCache
vtkOpenGLVertexBufferObjectCache
vtkShaderProgram
vtkOpenGLVertexBufferObject
vtkOpenGLIndexBufferObject
vtkOpenGLVertexArrayObject
vtkOpenGLFramebufferObject

4. 事件桥接:QVTKRenderWindowAdapter

QVTKOpenGLNativeWidget 本身只做 QOpenGLWidget 生命周期,实际的上下文桥接在内部类 QVTKRenderWindowAdapter 中完成。

构造函数注册观察者(QVTKRenderWindowAdapter.cxx:110-121):

cpp 复制代码
renWin->AddObserver(vtkCommand::WindowMakeCurrentEvent, ...);
renWin->AddObserver(vtkCommand::WindowIsCurrentEvent,  ...);
renWin->AddObserver(vtkCommand::WindowFrameEvent,       ...);
renWin->AddObserver(vtkCommand::StartEvent,             ...);
renWin->AddObserver(vtkCommand::EndEvent,               ...);
renWin->AddObserver(vtkCommand::CursorChangedEvent,     ...);

事件分发映射(QVTKRenderWindowAdapter.cxx:451-486):

VTK 事件 Qt 侧动作
WindowMakeCurrentEvent QOpenGLContext::makeCurrent(surface) ------ 让 Qt 上下文成为 current
WindowIsCurrentEvent 检查 QOpenGLContext::currentContext() 是否是自己,写回 bool*
WindowFrameEvent widget->update() 触发重绘
CursorChangedEvent 把 VTK 光标形状转成 QCursor 设置到 widget

这样,vtkGenericOpenGLRenderWindow::Render() 里调用 MakeCurrent() → 触发事件 → Qt 把上下文设为 current → VTK 继续用 glXXX 渲染,就自动落在了 Qt 的上下文上。

5. OpenGL 函数指针解析

VTK 需要拿到 GL 函数地址。因为上下文是 Qt 创建的,VTK 不用平台绑定,而是把自己的符号加载器指向 Qt 的 getProcAddress(QVTKOpenGLNativeWidget.cxx:221-234):

cpp 复制代码
auto loadFunc = [](void* userData, const char* name) -> vtkOpenGLRenderWindow::VTKOpenGLAPIProc {
  if (auto* context = reinterpret_cast<QOpenGLContext*>(userData))
    if (auto* symbol = context->getProcAddress(name)) return symbol;
  return nullptr;
};
this->RenderWindow->SetOpenGLSymbolLoader(loadFunc, this->context());
this->RenderWindow->vtkOpenGLRenderWindow::OpenGLInit();

第二部分:全生命周期渲染流程

以 QVTKOpenGLNativeWidget + vtkGenericOpenGLRenderWindow + 一个 vtkActor/vtkPolyDataMapper/vtkPolyData 的典型场景为例。

6. 阶段一:对象创建与组装

cpp 复制代码
// 在 QApplication 之前设置默认 surface format
QSurfaceFormat::setDefaultFormat(QVTKOpenGLNativeWidget::defaultFormat());

vtkNew<vtkGenericOpenGLRenderWindow> renWin;   // (A) 渲染窗口
QVTKOpenGLNativeWidget* widget = new QVTKOpenGLNativeWidget(renWin, parent);

vtkNew<vtkRenderer> renderer;                   // (B) 渲染器
renWin->AddRenderer(renderer);

vtkNew<vtkActor> actor;                         // (C) Actor
renderer->AddActor(actor);

vtkNew<vtkPolyDataMapper> mapper;               // (D) Mapper
actor->SetMapper(mapper);
mapper->SetInputConnection(source->GetOutputPort()); // (E) 数据源(pipeline 上游)

6.1 渲染窗口创建(两个构造函数)

QVTKOpenGLNativeWidget 有两个构造路径(QVTKOpenGLNativeWidget.cxx:27):

  • 无参构造:内部 vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New() 自建一个;
  • 带参构造:接受外部传入的 vtkGenericOpenGLRenderWindow*。

构造过程(QVTKOpenGLNativeWidget.cxx:35):设置 StrongFocus、NoPartialUpdate、MouseTracking;connect(resized → updateSize);调用 setRenderWindow(win)。

6.2 setRenderWindow 强制类型检查

QVTKOpenGLNativeWidget.cxx:78-87 先做 SafeDownCast,非 vtkGenericOpenGLRenderWindow 直接拒绝:

cpp 复制代码
auto gwin = vtkGenericOpenGLRenderWindow::SafeDownCast(win);
if (win != nullptr && gwin == nullptr)
  qDebug() << "QVTKOpenGLNativeWidget requires a vtkGenericOpenGLRenderWindow. ...";

真正的 setRenderWindow(vtkGenericOpenGLRenderWindow*)(QVTKOpenGLNativeWidget.cxx:90)做三件事:

  1. SetReadyForRendering(false) ------ 上下文就绪前禁止渲染;
  2. SetFrameBlitModeToNoBlit() ------ 关键:VTK 只渲染到内部 FBO,不做硬件 blit,上屏交给 paintGL;
  3. 若无 interactor,自动创建 QVTKInteractor 并 Initialize(),再挂 vtkInteractorStyleTrackballCamera。

7. 阶段二:OpenGL 上下文初始化

QOpenGLWidget 首次显示时,Qt 创建 QOpenGLContext 并回调 initializeGL()。核心在 QVTKOpenGLNativeWidget.cxx:212。

7.1 符号加载器指向 Qt 的 getProcAddress

见上文第 5 节(SetOpenGLSymbolLoader + OpenGLInit)。

7.2 OpenGLInit → OpenGLInitContext + OpenGLInitState

vtkOpenGLRenderWindow.cxx:811:

  • OpenGLInitContext()(vtkOpenGLRenderWindow.cxx:904):通过 gladLoadGLUserPtr(SymbolLoader.LoadFunction, UserData) 用上面的 Qt 回调加载所有 GL 函数指针 ,成功后置 Initialized = true;同时读取硬件最大线宽、可选启用 debug 输出。
  • OpenGLInitState()(vtkOpenGLRenderWindow.cxx:826):vtkOpenGLState::Initialize(this) 建立状态缓存,设置 GL_UNPACK_ALIGNMENT/GL_PACK_ALIGNMENT = 1。

7.3 状态复位 + 构造适配器

初始化后半段(QVTKOpenGLNativeWidget.cxx:236):

cpp 复制代码
auto ostate = this->RenderWindow->GetState();
ostate->Reset();
ostate->vtkglDepthFunc(GL_LEQUAL);      // Qt 默认 GL_LESS,VTK 要 GL_LEQUAL
ostate->vtkglEnable(GL_DEPTH_TEST);     // Qt 默认关闭深度测试,VTK 要开启

this->RenderWindowAdapter.reset(
  new QVTKRenderWindowAdapter(this->context(), this->RenderWindow, this));

QVTKRenderWindowAdapter 构造函数(QVTKRenderWindowAdapter.cxx:78)完成真正桥接:

  1. 保存 QOpenGLContext 与 surface;
  2. 注册 6 个事件观察者(见第 4 节);
  3. 关掉 VTK 的 UseOffScreenBuffers(因为适配器自己用 FBO);
  4. SetForceMaximumHardwareLineWidth(1)、SetReadyForRendering(true)、SetOwnContext(0);
  5. OpenGLInitContext()(再次确保上下文就绪)。

7.4 初始化时序图(Mermaid)

OpenGL 驱动 / GPU vtkGenericOpenGLRenderWindow QVTKRenderWindowAdapter QVTKOpenGLNativeWidget QOpenGLWidget OpenGL 驱动 / GPU vtkGenericOpenGLRenderWindow QVTKRenderWindowAdapter QVTKOpenGLNativeWidget QOpenGLWidget #mermaid-svg-XTzrTU8pTryqPJrC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XTzrTU8pTryqPJrC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XTzrTU8pTryqPJrC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XTzrTU8pTryqPJrC .error-icon{fill:#552222;}#mermaid-svg-XTzrTU8pTryqPJrC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XTzrTU8pTryqPJrC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XTzrTU8pTryqPJrC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XTzrTU8pTryqPJrC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XTzrTU8pTryqPJrC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XTzrTU8pTryqPJrC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XTzrTU8pTryqPJrC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XTzrTU8pTryqPJrC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XTzrTU8pTryqPJrC .marker.cross{stroke:#333333;}#mermaid-svg-XTzrTU8pTryqPJrC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XTzrTU8pTryqPJrC p{margin:0;}#mermaid-svg-XTzrTU8pTryqPJrC .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XTzrTU8pTryqPJrC text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-XTzrTU8pTryqPJrC .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XTzrTU8pTryqPJrC .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-XTzrTU8pTryqPJrC .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-XTzrTU8pTryqPJrC .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-XTzrTU8pTryqPJrC #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-XTzrTU8pTryqPJrC .sequenceNumber{fill:white;}#mermaid-svg-XTzrTU8pTryqPJrC #sequencenumber{fill:#333;}#mermaid-svg-XTzrTU8pTryqPJrC #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-XTzrTU8pTryqPJrC .messageText{fill:#333;stroke:none;}#mermaid-svg-XTzrTU8pTryqPJrC .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XTzrTU8pTryqPJrC .labelText,#mermaid-svg-XTzrTU8pTryqPJrC .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-XTzrTU8pTryqPJrC .loopText,#mermaid-svg-XTzrTU8pTryqPJrC .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-XTzrTU8pTryqPJrC .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XTzrTU8pTryqPJrC .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-XTzrTU8pTryqPJrC .noteText,#mermaid-svg-XTzrTU8pTryqPJrC .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-XTzrTU8pTryqPJrC .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XTzrTU8pTryqPJrC .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XTzrTU8pTryqPJrC .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XTzrTU8pTryqPJrC .actorPopupMenu{position:absolute;}#mermaid-svg-XTzrTU8pTryqPJrC .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-XTzrTU8pTryqPJrC .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XTzrTU8pTryqPJrC .actor-man circle,#mermaid-svg-XTzrTU8pTryqPJrC line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-XTzrTU8pTryqPJrC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} initializeGL()(首次显示由 Qt 回调) 就绪,等待 paintGL 触发渲染 QOpenGLContext 已由 Qt 创建 1 SetOpenGLSymbolLoader(Qt::getProcAddress) 2 OpenGLInit() → OpenGLInitContext() 3 gladLoadGLUserPtr(...) 加载全部 GL 函数指针 4 Initialized = true 5 new QVTKRenderWindowAdapter(context, renderWindow, this) 6 AddObserver(MakeCurrent / IsCurrent / Frame / Start / End / Cursor) 7 SetOwnContext(0) 8 SetReadyForRendering(true) 9 OpenGLInitContext() 10

8. 阶段三:一帧渲染的完整调用链

8.1 入口:paintGL

QVTKOpenGLNativeWidget.cxx:272:

cpp 复制代码
void QVTKOpenGLNativeWidget::paintGL() {
  Superclass::paintGL();
  if (this->RenderWindow) {
    auto ostate = this->RenderWindow->GetState();
    ostate->Reset(); ostate->Push();
    ostate->vtkglDepthFunc(GL_LEQUAL);
    this->RenderWindowAdapter->paint();      // (A) 可能触发 VTK 渲染
    this->makeCurrent();                      // 渲染可能切走了上下文
    this->RenderWindowAdapter->blit(          // (B) 内部 FBO → framebuffer 0
      this->defaultFramebufferObject(), GL_COLOR_ATTACHMENT0, ...);
    ostate->Pop();
  }
}

Adapter::paint(QVTKRenderWindowAdapter.cxx:241)只有 DoVTKRenderInPaintGL 为 true 时才真正渲染(避免 Qt 反复 paint 时重复渲染):

cpp 复制代码
if (this->DoVTKRenderInPaintGL) {
  if (auto iren = this->RenderWindow->GetInteractor()) iren->Render();
  else this->RenderWindow->Render();
}

iren->Render() → vtkRenderWindowInteractor.cxx:168 → RenderWindow->Render()。

8.2 渲染窗口三层 Render

第一层 vtkGenericOpenGLRenderWindow::Render (vtkGenericOpenGLRenderWindow.cxx:187)------负责状态管理:

cpp 复制代码
void vtkGenericOpenGLRenderWindow::Render() {
  if (this->ReadyForRendering) {
    if (!this->Initialized) this->OpenGLInit();
    this->MakeCurrent();                     // → 事件 → Qt makeCurrent
    if (!this->IsCurrent()) { /* 跳过 */ }   // → 事件 → Qt 判断
    else {
      auto state = this->GetState();
      state->Reset(); state->Push();
      this->Superclass::Render();            // 第二层
      state->Pop();
    }
  }
}

第二层 vtkOpenGLRenderWindow::Render (vtkOpenGLRenderWindow.cxx:3049)------记录首次渲染时间给 shader cache 用,然后调 Superclass::Render()。

第三层 vtkRenderWindow::Render (vtkRenderWindow.cxx:414)------主循环:

cpp 复制代码
void vtkRenderWindow::Render() {
  if (this->InRender || this->InAbortCheck) return;   // 重入保护
  if (0 == Size[0] && 0 == Size[1]) SetSize(300, 300);
  this->InRender = 1;
  this->InvokeEvent(vtkCommand::StartEvent, nullptr);
  this->Start();                        // 确保上下文存在
  this->DoStereoRender();               // ← 渲染器集合在这里渲染
  this->End();
  this->CopyResultFrame();              // → InvokeEvent(RenderEvent) + Frame()
  this->InRender = 0;
  this->InvokeEvent(vtkCommand::EndEvent, nullptr);
}

DoStereoRender()(vtkRenderWindow.cxx:476)遍历所有 renderer,调用 Renderers->Render(),即 vtkRendererCollection::Render() → 每个 vtkRenderer::Render()。

8.3 渲染器层

vtkRenderer::Render()(vtkRenderer.cxx:244):

  1. Draw 关闭则直接返回;
  2. BackingStore 命中且无修改 → 直接用缓存图 SetPixelData 返回;
  3. 可见性剔除 :遍历 props,把 GetVisibility() 为真的放进 PropArray;
  4. AllocateTime():调用 culler 排序、为每个 prop 分配渲染时间;
  5. ActiveCamera->UpdateIdealShiftScale();
  6. DeviceRender() ------ 虚函数 ,OpenGL2 后端进入 vtkOpenGLRenderer::DeviceRender。

vtkOpenGLRenderer::DeviceRender()(vtkOpenGLRenderer.cxx:313):

cpp 复制代码
if (this->Pass != nullptr) { /* 自定义 render pass */ }
else {  // 默认路径
  this->RenderWindow->MakeCurrent();   // ← 事件 → Qt makeCurrent
  this->UpdateCamera();                // 含 Clear()
  this->UpdateLightGeometry();
  this->UpdateLights();
  this->UpdateGeometry();
}

Clear 发生在相机更新里 :UpdateCamera() → vtkOpenGLCamera::Render(vtkOpenGLCamera.cxx:40)设置 viewport/scissor 后,当 GetErase() 为真(默认)时调用 ren->Clear() → vtkOpenGLRenderer::Clear()(vtkOpenGLRenderer.cxx:749)执行 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)。

8.4 几何渲染:UpdateGeometry 分阶段

vtkOpenGLRenderer::UpdateGeometry()(vtkOpenGLRenderer.cxx:405)按顺序:

  1. 拾取分支 (Selector 存在时走硬件选择,不画真实场景);
  2. 阴影分支 (UseShadows → shadow map pass);
  3. 默认路径:
    • Opaque 不透明几何 :DeviceRenderOpaqueGeometry()(vtkOpenGLRenderer.cxx:570)→ 默认走 Superclass::DeviceRenderOpaqueGeometry() → UpdateOpaquePolygonalGeometry()(遍历 props 调 RenderOpaqueGeometry);
    • Translucent 半透明几何 :若有半透明 prop 再走 DeviceRenderTranslucentPolygonalGeometry(含深度剥离 depth peeling 选项);
  4. FXAA(若启用);
  5. 体绘制 :RenderVolumetricGeometry;
  6. Overlay :RenderOverlay。

8.5 Actor 层

vtkActor::RenderOpaqueGeometry()(vtkActor.cxx:181):

cpp 复制代码
this->Property->Render(this, ren);       // 设置光照/颜色等
if (this->BackfaceProperty) BackfaceProperty->BackfaceRender(...);
if (this->Texture) this->Texture->Render(ren);
this->Render(ren, this->Mapper);          // ← 虚函数 → vtkOpenGLActor::Render
this->Property->PostRender(this, ren);

vtkOpenGLActor::Render()(vtkOpenGLActor.cxx:44):

cpp 复制代码
bool opaque = !this->IsRenderingTranslucentPolygonalGeometry();
if (opaque) ostate->vtkglDepthMask(GL_TRUE);
else        ostate->vtkglDepthMask(GL_FALSE);  // 半透明关闭深度写入

mapper->Render(ren, this);                     // ← 触发 pipeline 更新 + 绘制

if (!opaque) ostate->vtkglDepthMask(GL_TRUE);

8.6 Mapper 层:RenderPiece

mapper->Render(ren, actor) 经 vtkMapper::Render 基类分发到 vtkOpenGLPolyDataMapper::RenderPiece(vtkOpenGLPolyDataMapper.cxx:3621):

cpp 复制代码
void vtkOpenGLPolyDataMapper::RenderPiece(vtkRenderer* ren, vtkActor* actor) {
  this->ResourceCallback->RegisterGraphicsResources(renWin);
  this->CurrentInput = this->GetInput();
  if (!this->Static) this->GetInputAlgorithm()->Update();   // ★ pipeline 执行
  if (!this->CurrentInput->GetPoints()) return;
  this->UpdateCameraShiftScale(ren, actor);
  this->RenderPieceStart(ren, actor);   // 准备
  this->RenderPieceDraw(ren, actor);    // 绘制
  this->RenderPieceFinish(ren, actor);  // 收尾
}
8.6.1 RenderPieceStart(3387)
  • 设置点大小 glPointSize;
  • UpdateBufferObjects(ren, actor) ------ 构建/复用 VBO、IBO、cell 纹理;
  • 激活 cell scalar/normal/edge 纹理;若按纹理着色则加载颜色纹理。
8.6.2 RenderPieceDraw(3478)

遍历图元类别(Verts → Lines → Polys → Strips),对每个有索引的图元:

cpp 复制代码
this->UpdateShaders(this->Primitives[i], ren, actor);   // 编译/复用 shader + 设 uniform
this->Primitives[i].IBO->Bind();                         // 绑索引缓冲
glDrawRangeElements(mode, 0, numVerts-1, indexCount, GL_UNSIGNED_INT, nullptr);
this->Primitives[i].IBO->Release();
8.6.3 BuildBufferObjects(3893)
  • MapScalars(1.0):把标量按 LUT/颜色函数映射成颜色数组;
  • 决定 cell/point 标量、法线来源;
  • 通过 MTime 状态缓存 判断哪些缓冲需要重建(CellTextureBuildState 等);
  • VBOs->CacheDataArray("vertexMC"/"normalMC"/"tcoord"/"scalarColor"/...):把数据上传到 vtkOpenGLVertexBufferObjectCache(跨 mapper 共享缓存);
  • CellCellMap->BuildPrimitiveOffsetsIfNeeded()。
8.6.4 UpdateShaders(2617)

由 vtkOpenGLShaderCache 管理:根据 property/光照/场景拼接 GLSL,命中缓存则复用 program,否则编译链接;随后写入 uniform(MCVCMatrix、法线矩阵、光照参数、LUT 纹理等)。

8.7 帧尾:CopyResultFrame + Frame

回到 vtkRenderWindow::Render 尾部 → CopyResultFrame()(vtkRenderWindow.cxx:710):

cpp 复制代码
this->InvokeEvent(vtkCommand::RenderEvent);
this->Frame();   // ★

vtkGenericOpenGLRenderWindow::Frame()(vtkGenericOpenGLRenderWindow.cxx:73):

cpp 复制代码
void vtkGenericOpenGLRenderWindow::Frame() {
  this->Superclass::Frame();                          // vtkOpenGLRenderWindow::Frame
  this->InvokeEvent(vtkCommand::WindowFrameEvent, nullptr);  // → adapter->frame()
}
  • vtkOpenGLRenderWindow::Frame()(vtkOpenGLRenderWindow.cxx:1583):把 RenderFramebuffer 解析(MSAA resolve)/翻转(FlipY)到 DisplayFramebuffer。由于前面设了 FrameBlitMode = NoBlit,不会直接 blit 到硬件窗口。
  • WindowFrameEvent → 适配器 frame()(QVTKRenderWindowAdapter.cxx:268)→ widget->update() 排入一次 paint。

8.9 多帧与交互时序图(Mermaid)

OpenGL 驱动 / GPU vtkGenericOpenGLRenderWindow vtkInteractorStyleTrackballCamera vtkRenderWindowInteractor QVTKInteractorAdapter QVTKRenderWindowAdapter QVTKOpenGLNativeWidget QWidget 事件循环 OpenGL 驱动 / GPU vtkGenericOpenGLRenderWindow vtkInteractorStyleTrackballCamera vtkRenderWindowInteractor QVTKInteractorAdapter QVTKRenderWindowAdapter QVTKOpenGLNativeWidget QWidget 事件循环 #mermaid-svg-6qDTgOFJ6ZHEUnh0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .error-icon{fill:#552222;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .marker.cross{stroke:#333333;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 p{margin:0;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .sequenceNumber{fill:white;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 #sequencenumber{fill:#333;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .messageText{fill:#333;stroke:none;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .labelText,#mermaid-svg-6qDTgOFJ6ZHEUnh0 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .loopText,#mermaid-svg-6qDTgOFJ6ZHEUnh0 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .noteText,#mermaid-svg-6qDTgOFJ6ZHEUnh0 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .actorPopupMenu{position:absolute;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 .actor-man circle,#mermaid-svg-6qDTgOFJ6ZHEUnh0 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-6qDTgOFJ6ZHEUnh0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 鼠标拖动交互(一次 Rotate = 多帧渲染循环) ... 拖动中反复执行上面渲染循环 ... QMouseEvent(QEvent::MouseMove) 1 handleEvent(evt) 2 ProcessEvent(e, iren) 3 InvokeEvent(MouseMoveEvent, e) 4 OnMouseMove() 5 状态机:Rotate / Pan / Dolly 6 InvokeEvent(InteractionEvent) 7 rwi->>Render() 8 InvokeEvent(WindowMakeCurrentEvent) 9 makeCurrent(surface) 10 渲染一帧(同 §8 渲染流程) 11 InvokeEvent(WindowFrameEvent) 12 widget->>update() 13 QMouseEvent(MouseButtonRelease) 14 handleEvent(evt) 15 ProcessEvent(e, iren) 16 InvokeEvent(LeftButtonReleaseEvent) 17 OnLeftButtonUp() → EndRotate() 18 InvokeEvent(EndInteractionEvent) 19

9. 阶段四:Framebuffer 上屏(blit)

回到 paintGL,RenderWindowAdapter->blit(QVTKRenderWindowAdapter.cxx:301):

cpp 复制代码
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetId);   // targetId = defaultFramebufferObject() = 0
this->RenderWindow->BlitDisplayFramebuffer(
  0, 0, 0, rbsize[0], rbsize[1], targetRect.x(), ..., GL_COLOR_BUFFER_BIT, GL_LINEAR);
this->clearAlpha(targetRect);

把 VTK 内部 DisplayFramebuffer 的内容 blit 到 QOpenGLWidget 的 framebuffer 0,由 Qt 合成上屏。

双 FBO 的意义 :VTK 渲染进独立于 QOpenGLWidget 的 FBO,最后再 blit 出来------这样 VTK 里临时渲染到 back-buffer(如拾取/选择)不会破坏屏幕上已合成的内容。

10. 阶段五:退出与资源清理

10.1 Widget 析构

~QVTKOpenGLNativeWidget()(QVTKOpenGLNativeWidget.cxx:71):

cpp 复制代码
this->makeCurrent();
this->cleanupContext();   // → RenderWindowAdapter.reset(nullptr)

cleanupContext() 还会在 QOpenGLContext::aboutToBeDestroyed 信号触发时被调用(QVTKOpenGLNativeWidget.cxx:258),确保 GL 资源在上下文销毁前释放。

10.2 适配器析构

~QVTKRenderWindowAdapter → Internals.reset → ~QVTKInternals(QVTKRenderWindowAdapter.cxx:150):

cpp 复制代码
for (const auto& id : this->RenderWindowObserverIds)
  this->RenderWindow->RemoveObserver(id);     // 1. 移除所有事件观察者
this->RenderWindow->Finalize();               // 2. 释放 VTK 侧 GL 资源
this->RenderWindow->SetReadyForRendering(false); // 3. 禁止后续渲染

10.3 VTK 资源释放

vtkGenericOpenGLRenderWindow::Finalize()(vtkGenericOpenGLRenderWindow.cxx:65)→ ReleaseGraphicsResources(this) → vtkOpenGLRenderWindow::ReleaseGraphicsResources()(vtkOpenGLRenderWindow.cxx:722):

  • PushContext()(进入本上下文)后释放:
    • 各种全屏 quad(ResolveQuad / DepthBlitQuad / FlipQuad / DepthReadQuad);
    • RenderFramebuffer / DisplayFramebuffer / ResolveFramebuffer / DepthFramebuffer 的 GL 资源;
    • 所有注册的 vtkGenericOpenGLResourceFreeCallback(mapper 的 buffer 回调在这里释放 VBO/IBO);
    • 每个 renderer 的 ReleaseGraphicsResources;
    • ShaderCache->ReleaseGraphicsResources(释放所有 shader program);
  • State->VerifyNoActiveTextures();
  • PopContext();
  • State->Delete() + 重建空 state,Initialized = false ------ 上下文标记失效。

10.4 渲染窗口析构

~vtkGenericOpenGLRenderWindow()(vtkGenericOpenGLRenderWindow.cxx:28):

cpp 复制代码
this->Finalize();          // 再兜底释放一次
// 遍历 renderers,逐个 ren->SetRenderWindow(nullptr) 解除关联

10.5 退出清理时序图(Mermaid)

OpenGL 驱动 / GPU vtkGenericOpenGLRenderWindow QVTKRenderWindowAdapter QVTKOpenGLNativeWidget OpenGL 驱动 / GPU vtkGenericOpenGLRenderWindow QVTKRenderWindowAdapter QVTKOpenGLNativeWidget #mermaid-svg-nyBsHahkNzilVYNA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nyBsHahkNzilVYNA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nyBsHahkNzilVYNA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nyBsHahkNzilVYNA .error-icon{fill:#552222;}#mermaid-svg-nyBsHahkNzilVYNA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nyBsHahkNzilVYNA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nyBsHahkNzilVYNA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nyBsHahkNzilVYNA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nyBsHahkNzilVYNA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nyBsHahkNzilVYNA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nyBsHahkNzilVYNA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nyBsHahkNzilVYNA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nyBsHahkNzilVYNA .marker.cross{stroke:#333333;}#mermaid-svg-nyBsHahkNzilVYNA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nyBsHahkNzilVYNA p{margin:0;}#mermaid-svg-nyBsHahkNzilVYNA .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nyBsHahkNzilVYNA text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-nyBsHahkNzilVYNA .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-nyBsHahkNzilVYNA .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-nyBsHahkNzilVYNA .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-nyBsHahkNzilVYNA .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-nyBsHahkNzilVYNA #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-nyBsHahkNzilVYNA .sequenceNumber{fill:white;}#mermaid-svg-nyBsHahkNzilVYNA #sequencenumber{fill:#333;}#mermaid-svg-nyBsHahkNzilVYNA #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-nyBsHahkNzilVYNA .messageText{fill:#333;stroke:none;}#mermaid-svg-nyBsHahkNzilVYNA .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nyBsHahkNzilVYNA .labelText,#mermaid-svg-nyBsHahkNzilVYNA .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-nyBsHahkNzilVYNA .loopText,#mermaid-svg-nyBsHahkNzilVYNA .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-nyBsHahkNzilVYNA .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-nyBsHahkNzilVYNA .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-nyBsHahkNzilVYNA .noteText,#mermaid-svg-nyBsHahkNzilVYNA .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-nyBsHahkNzilVYNA .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nyBsHahkNzilVYNA .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nyBsHahkNzilVYNA .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nyBsHahkNzilVYNA .actorPopupMenu{position:absolute;}#mermaid-svg-nyBsHahkNzilVYNA .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-nyBsHahkNzilVYNA .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nyBsHahkNzilVYNA .actor-man circle,#mermaid-svg-nyBsHahkNzilVYNA line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-nyBsHahkNzilVYNA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 析构 或 aboutToBeDestroyed GL 资源已在上下文销毁前全部释放 ~QVTKOpenGLNativeWidget() 1 makeCurrent() 2 cleanupContext() → RenderWindowAdapter.reset 3 RemoveObserver(全部 6 个) 4 Finalize() → ReleaseGraphicsResources 5 释放 Render / Display / Resolve / Depth FBO 6 释放 Shader 缓存 / VBO / 纹理 7 State 重建 + Initialized = false 8 SetReadyForRendering(false) 9

第三部分:补充主题

11. 状态管理(易踩坑点)

  • 每次 paintGL 里 ostate->Reset() + Push/Pop,并强制 DepthFunc(GL_LEQUAL)、启用 GL_DEPTH_TEST,因为 Qt 默认是 GL_LESS 且关闭深度测试。
  • 上下文销毁时通过 aboutToBeDestroyed → cleanupContext() → 销毁适配器 → RenderWindow->Finalize() 释放所有 VTK GL 资源。

12. 是否是两个进程?

不是。 全部发生在同一个进程内,通常是同一个线程(主线程 / GUI 线程):

  • QVTKOpenGLNativeWidget、QVTKRenderWindowAdapter、vtkGenericOpenGLRenderWindow 都是同一个可执行文件里链接进来的代码,在同一个地址空间直接调用 C++ 方法。
  • 它们之间的"事件"是 VTK 观察者回调(AddObserver → 函数指针 / 成员函数回调),是普通函数调用,不是跨进程消息或 socket。
  • OpenGL 上下文也是同一个:QOpenGLContext 由 Qt 在本进程内创建,VTK 通过 SetOpenGLSymbolLoader + makeCurrent 直接使用它,getProcAddress 返回的是本进程内的地址。

唯一的"边界"是库的边界,不是进程边界:VTK 和 Qt 是两套独立编译的库,被同一个应用链接、加载进同一个进程。VTK 动态解析 GL 函数是为了不硬编码平台 / 驱动,而非跨进程。

补充:如果应用用 QOpenGLContext 的共享上下文(share context)做后台资源加载,可能会有第二个线程,但仍是同一进程;跨线程共享 GL 上下文时需做线程同步,同样是同进程内多线程,不是多进程。

13. 跨进程可行性

13.1 关键前提:OpenGL 上下文本身不能跨进程

GL 上下文(QOpenGLContext / HGLRC / EGLContext)绑定在进程地址空间 内,不能"把同一个上下文在两个进程里 makeCurrent"。所以 QVTKOpenGLNativeWidget + vtkGenericOpenGLRenderWindow 这套上下文适配逻辑只能发生在单进程内。

但"跨进程共享 GPU 渲染结果 / 纹理"完全可以实现,有几条成熟路径:

13.2 路径 1:零拷贝 GPU 对象导出 / 导入(同 GPU,现代做法)

把纹理内存和信号量做成 OS 级句柄,传给另一个进程导入:

  • Linux :EGL_EXT_image_dma_buf_import + DMA-BUF fd;或 GL_EXT_memory_object_fd / GL_EXT_semaphore_fd(OpenGL external memory / objects)。Wayland 合成器、PipeWire 均使用此方案。
  • Windows :D3D 共享纹理(IDXGIResource 共享句柄)经 WGL_NV_DX_interop2 或 GL_EXT_memory_object_win32 导入到 OpenGL;或直接用 Vulkan 外部内存 (VK_KHR_external_memory_win32 + VK_KHR_external_semaphore)中转。
  • 优点:真正零拷贝。
  • 缺点:复杂度高、平台 / 驱动相关、需要同 GPU 且驱动支持,VTK 原生不封装,需自行写 interop。

13.3 路径 2:共享内存像素拷贝(最简单通用)

A 进程离屏渲染 → glReadPixels → 写进 QSharedMemory / CreateFileMapping → B 进程 glTexSubImage2D 或直接绘制。

  • 经典做法:Chromium 浏览器进程模型(渲染进程先栅格化到 SHM,GPU 进程再上传为纹理)。
  • 优点:任何平台都能跑,与 VTK 集成最简单。
  • 缺点:有拷贝和带宽开销。

13.4 路径 3:窗口嵌入(跨进程显示同一个窗口)

A 进程通过 QWindow::fromWinId + SetParent(Windows)或 XEmbed(Linux)把渲染窗口嵌入 B 进程窗口。

  • 共享的是窗口,不是上下文;两个进程各有各的 GL 上下文,合成交给窗口系统。

13.5 路径 4:VTK 生态现成的"跨进程"方案 = 离屏渲染 + 图像传输

ParaView 的 client-server 模型:pvserver 在另一个进程里用 EGL / OSMesa 离屏渲染,把**图像(压缩后)**经 TCP 发给客户端显示。

  • 传的是图像,不是 GL 上下文。
  • VTK 对应:vtkEGLRayContext / offscreen rendering。

13.6 建议

  • 若目标只是"渲染进程和显示进程分离",先用 路径 2(共享内存 + 图像) 或 路径 4(离屏渲染 + 图像流),可靠、跨平台、改动小。
  • 仅当图像拷贝成为瓶颈(高分辨率高帧率、延迟敏感)时,才上 路径 1 的零拷贝 external memory / D3D interop,并接受平台锁定(Windows 上用 WGL / D3D interop)。

14. 对照:VTK 自管理上下文时的原生实现(Windows / WGL)

作为对照,当不用 Qt、让 VTK 自己创建窗口和上下文(OwnContext = 1)时,Windows 上由 vtkWin32OpenGLRenderWindow 用 WGL 原生实现第 2 节那套接口。它持有三个 Win32 句柄:

cpp 复制代码
// Rendering/OpenGL2/vtkWin32OpenGLRenderWindow.h
HGLRC ContextId;      // OpenGL 渲染上下文
HDC   DeviceContext;  // 设备上下文
HWND  WindowId;       // 窗口句柄

Windows 上一个"当前上下文"其实是 (HDC, HGLRC) 这一对,所以 PushContext / PopContext 用双栈保存(比 WebAssembly 版多一个 DC 栈):

cpp 复制代码
void PushContext() {
  HGLRC current = wglGetCurrentContext();
  this->ContextStack.push(current);
  this->DCStack.push(wglGetCurrentDC());
  if (current != this->ContextId) this->MakeCurrent();
}
void PopContext() {
  HGLRC target = this->ContextStack.top();
  HDC dc = this->DCStack.top();
  this->ContextStack.pop(); this->DCStack.pop();
  if (target != current) wglMakeCurrent(dc, target);
}

MakeCurrent / ReleaseCurrent / IsCurrent 只是对 wglMakeCurrent / wglGetCurrentContext 的封装,带"已是 current 就跳过"的短路优化(vtkRenderWindow::SetForceMakeCurrent() 可强制跳过该检查)。

创建上下文的流程(CreateAWindow → SetupPixelFormatPaletteAndContext):

  1. 注册窗口类 → CreateWindowW 创建 HWND → GetDC 拿 HDC。
  2. 先建临时假窗口 + 假 context ,用 gladLoaderLoadWGL 加载 WGL 扩展(wglChoosePixelFormatARB 等必须先有 current context 才能解析)。
  3. wglChoosePixelFormatARB 按双缓冲 / 颜色 / 深度 / 模板 / 立体 / sRGB 选像素格式。
  4. 优先 wglCreateContextAttribsARB 从 4.5 往下逐个尝试建 3.2+ core context(对 Intel SandyBridge 有特判黑名单),失败回退 wglCreateContext。
  5. 销毁临时假窗口 / 假 context。

销毁时同样遵守 OwnContext 约定:只有 OwnContext && ContextId 才 wglDeleteContext,且先 wglMakeCurrent(dc, nullptr) 解绑再删除。

同一套接口在 WebAssembly 下由 vtkWebAssemblyOpenGLRenderWindow 用 Emscripten 的 emscripten_webgl_create_context / emscripten_webgl_make_context_current / emscripten_webgl_destroy_context 实现,逻辑同构,只是把 (HDC, HGLRC) 换成一个 ContextId 句柄、把 WGL 换成 WebGL2(canvas 自带多采样被关闭,因为 VTK 自己用离屏多采样 FBO + resolve)。


第四部分:实例 ------ 渲染一个三角面片(完整 OpenGL 调用)

以一个最小三角面片(3 个顶点 + 1 个三角形 cell)为例,把前面所有章节的调用链落到具体的 OpenGL 函数调用 。GL 调用点均来自 VTK v9.5.0 源码,标注了 文件:行号。

15. 场景设定

cpp 复制代码
vtkNew<vtkPoints> pts;
pts->InsertNextPoint(0, 0, 0);   // P0
pts->InsertNextPoint(1, 0, 0);   // P1
pts->InsertNextPoint(0, 1, 0);   // P2

vtkNew<vtkCellArray> tris;
vtkIdType tri[3] = { 0, 1, 2 };
tris->InsertNextCell(3, tri);

vtkNew<vtkPolyData> mesh;
mesh->SetPoints(pts);
mesh->SetPolys(tris);

vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputData(mesh);       // 静态数据,pipeline 一次更新后不再重复

vtkNew<vtkActor> actor;
actor->SetMapper(mapper);
// renderer->AddActor(actor); renWin->AddRenderer(renderer); ...(见第 6 节)

这个 3 顶点 + 1 三角形的模型,最终要变成一张绘制命令,经历下面从 CPU 数据到 GPU 缓冲、再到 glDrawRangeElements 的完整过程。

16. 从 vtkPolyData 到 GPU 缓冲的内存布局

VTK 的 vtkOpenGLPolyDataMapper 把每个属性放进独立的 VBO (非交错)。BuildBufferObjects(vtkOpenGLPolyDataMapper.cxx:3893)里:

cpp 复制代码
VBOs->CacheDataArray("vertexMC",   points->GetData(),  cache, VTK_FLOAT);        // 位置
VBOs->CacheDataArray("normalMC",   normals,            cache, VTK_FLOAT);        // 法线
VBOs->CacheDataArray("tcoord",     tcoords,            cache, VTK_FLOAT);        // 纹理坐标
VBOs->CacheDataArray("scalarColor", colors,            cache, VTK_UNSIGNED_CHAR); // 颜色

对应到 GL 侧(本实例:3 顶点、1 三角形):

属性名(shader 输入) 组件数 GL 类型 每顶点字节 本实例数据量 说明
vertexMC 3 GL_FLOAT 12 9 floats = 36 B 模型坐标位置
normalMC 3 GL_FLOAT 12 9 floats = 36 B 法线(可选)
tcoord 2 GL_FLOAT 8 6 floats = 24 B 纹理坐标(可选)
scalarColor 4 GL_UNSIGNED_BYTE(归一化) 4 12 B RGBA(可选)

索引缓冲 IBO:GL_ELEMENT_ARRAY_BUFFER,GL_UNSIGNED_INT,内容 [0, 1, 2] = 12 字节。

17. 完整调用序列(按发生顺序)

17.1 上下文与清屏(每帧开头)

由 vtkOpenGLCamera::Render(vtkOpenGLCamera.cxx:40)→ vtkOpenGLRenderer::Clear(vtkOpenGLRenderer.cxx:749):

c 复制代码
glViewport(x, y, w, h);
glEnable(GL_SCISSOR_TEST);  glScissor(x, y, w, h);
glClearColor(bgR, bgG, bgB, 1.0f);
glClearDepth(1.0);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);      // Qt 默认 GL_LESS,VTK 强制 LEQUAL
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

17.2 Pipeline 更新(CPU 阶段,无 GL 调用)

vtkOpenGLPolyDataMapper::RenderPiece(3621)里 GetInputAlgorithm()->Update(),产出内存中的 vtkPolyData(3 点 + 1 三角)。静态数据(Static=1)时此步被跳过。

17.3 缓冲对象创建(首次或脏时)

基类 vtkOpenGLBufferObject:

c 复制代码
// 顶点位置 VBO(vtkOpenGLBufferObject.cxx:235,173-176)
glGenBuffers(1, &vboVertex);
glBindBuffer(GL_ARRAY_BUFFER, vboVertex);
glBufferData(GL_ARRAY_BUFFER, 36, vertexData, GL_STATIC_DRAW);   // 9 floats

// 法线 VBO / 颜色 VBO 同上,各自独立 handle
glGenBuffers(1, &vboNormal);  glBufferData(GL_ARRAY_BUFFER, 36, normalData, GL_STATIC_DRAW);
glGenBuffers(1, &vboColor);   glBufferData(GL_ARRAY_BUFFER, 12, colorData,  GL_STATIC_DRAW);

// 索引 IBO(GL_ELEMENT_ARRAY_BUFFER,unsigned int [0,1,2])
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 12, indices, GL_STATIC_DRAW);

后续帧数据未变时,走 glBufferSubData(vtkOpenGLBufferObject.cxx:263)只更新变化部分,而不是重新 glBufferData。

17.4 VAO 与顶点属性绑定

vtkOpenGLVertexArrayObject(vtkOpenGLVertexArrayObject.cxx:90,151,165-167):

c 复制代码
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

// 对每个属性(index 由 17.6 的 glGetAttribLocation 得到):
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, /*stride*/0, /*offset*/0);   // vertexMC
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);                        // normalMC
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);                // scalarColor(归一化)

glBindVertexArray(0);

硬件 VAO 不可用时会走模拟路径 (vtkOpenGLVertexArrayObject.cxx:153-183),每次绘制前重新执行上面这组 glVertexAttribPointer。

17.5 Shader 编译链接(首次,之后命中缓存)

vtkShader::Compile(vtkShader.cxx:92-103)逐阶段:

c 复制代码
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vs, 1, &srcVS, nullptr);
glCompileShader(vs);
glGetShaderiv(vs, GL_COMPILE_STATUS, &ok);   // 失败读 GL_INFO_LOG_LENGTH + 日志

GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);  // 同上

vtkShaderProgram(vtkShaderProgram.cxx:207,301,491-492):

c 复制代码
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
glGetProgramiv(prog, GL_LINK_STATUS, &ok);

编译结果由 vtkOpenGLShaderCache 按"shader 源码 + 状态"为 key 缓存,后续帧直接复用 prog,不再重编。

17.6 属性位置解析(GLSL 变量名 → 位置)

VTK 的 shader 直接声明具名输入(vtkOpenGLPolyDataMapper.cxx:2098,2360):

glsl 复制代码
in vec3 vertexMC;
in vec3 normalMC;
in vec4 scalarColor;
...
gl_Position = MCDCMatrix * vertexMC;      // 模型坐标 → 裁剪坐标
vertexVCVSOutput = MCVCMatrix * vertexMC; // 模型坐标 → 视图坐标(用于光照)

链接后,vtkShaderProgram::FindAttributeArray("vertexMC") 调 glGetAttribLocation(vtkShaderProgram.cxx:1075)得到索引,缓存进 AttributeLocs,再由 vtkOpenGLVertexArrayObject::AddAttributeArray(vtkOpenGLVertexArrayObject.cxx:292)把这个索引写进 VAO 属性的 Index------17.4 里的 glVertexAttribPointer 用的就是这个索引。

17.7 使用程序 + 上传 uniform

每帧 RenderPieceDraw → UpdateShaders(vtkOpenGLPolyDataMapper.cxx:2617)→ vtkShaderProgram::Bind(vtkShaderProgram.cxx:510,557):

c 复制代码
glUseProgram(prog);

随后 SetMapperShaderParameters(vtkOpenGLPolyDataMapper.cxx:2709)上传矩阵(3038-3106):

c 复制代码
glUniformMatrix4fv(locMCDCMatrix, 1, GL_FALSE, mcdc);  // 模型→裁剪(gl_Position 用)
glUniformMatrix4fv(locMCVCMatrix, 1, GL_FALSE, mcvc);  // 模型→视图(光照用)
glUniformMatrix3fv(locNormalMatrix, 1, GL_FALSE, norm); // 3x3 法线矩阵

光照/颜色相关(vtkShaderProgram 的 SetUniform3fv/4fv,vtkShaderProgram.cxx:876-1037):

c 复制代码
glUniform3fv(locLightPos,  1, lightPos);
glUniform3fv(locLightDiff, 1, lightDiffuse);
glUniform4fv(locAmbient,   1, ambientColor);

若按标量着色,还会绑定 LUT 纹理:

c 复制代码
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_1D, lutTexture);       // 或 GL_TEXTURE_2D
glUniform1i(locColorLUT, 0);                    // sampler 指向纹理单元 0

矩阵以列主序 上传(VTK 内部行主序,上传前 Transpose(),见 vtkOpenGLCamera.cxx:122-126)。

17.8 绘制(draw call)

RenderPieceDraw(vtkOpenGLPolyDataMapper.cxx:3478,3523-3525):

c 复制代码
// VAO 在 UpdateShaders 开头已 bind:
glBindVertexArray(vao);

// 绑索引缓冲
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

// 真正的绘制调用:
glDrawRangeElements(GL_TRIANGLES,     // 三角面片图元
                    0, 2,             // 索引范围 [min=0, max=2]
                    3,                // 索引个数(1 个三角形 = 3 个索引)
                    GL_UNSIGNED_INT,
                    nullptr);

这就是把 1 个三角形画出来的那一句。图元模式由 GetOpenGLMode(representation, i) 决定:VTK_SURFACE(默认)→ GL_TRIANGLES;VTK_WIREFRAME → GL_LINES;VTK_POINTS → GL_POINTS。

17.9 收尾与复用

c 复制代码
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);   // IBO->Release
glBindVertexArray(0);                        // RenderPieceFinish 里 VAO->Release
glUseProgram(0);                             // shader 切换时

vtkOpenGLState 缓存 current program / VAO / 绑定状态,避免重复的 GL 状态切换调用。

18. 一次绘制涉及的全部 GL 调用(汇总)

阶段 GL 调用 源码位置
清屏 glClear / glClearColor / glClearDepth / glViewport / glScissor vtkOpenGLRenderer.cxx:749、vtkOpenGLCamera.cxx:40
缓冲创建 glGenBuffers / glBindBuffer / glBufferData vtkOpenGLBufferObject.cxx:235,173-176
缓冲更新 glBufferSubData vtkOpenGLBufferObject.cxx:263
VAO glGenVertexArrays / glBindVertexArray vtkOpenGLVertexArrayObject.cxx:90,151
属性 glEnableVertexAttribArray / glVertexAttribPointer vtkOpenGLVertexArrayObject.cxx:165-167
Shader 编译 glCreateShader / glShaderSource / glCompileShader vtkShader.cxx:92-103
链接 glCreateProgram / glAttachShader / glLinkProgram vtkShaderProgram.cxx:207,301,491
属性位置 glGetAttribLocation vtkShaderProgram.cxx:1075
使用程序 glUseProgram vtkShaderProgram.cxx:557
uniform glUniformMatrix4fv / glUniformMatrix3fv / glUniform3fv / glUniform4fv / glUniform1i vtkShaderProgram.cxx:757-1037、vtkOpenGLPolyDataMapper.cxx:3038-3106
纹理 glActiveTexture / glBindTexture vtkTextureObject
绘制 glDrawRangeElements(GL_TRIANGLES, ...) vtkOpenGLPolyDataMapper.cxx:3523-3525
收尾 glBindVertexArray(0) / glBindBuffer(...,0) / glUseProgram(0) vtkOpenGLVertexArrayObject.cxx:190、vtkShaderProgram.cxx:663

19. 实例相关源码索引

主题 文件 行号
缓冲上传/创建 Rendering/OpenGL2/vtkOpenGLBufferObject.cxx 173-176 / 235 / 263
VAO + 属性指针 Rendering/OpenGL2/vtkOpenGLVertexArrayObject.cxx 90 / 151 / 165-167 / 190 / 292
Shader 编译 Rendering/OpenGL2/vtkShader.cxx 92-103
程序链接 Rendering/OpenGL2/vtkShaderProgram.cxx 207 / 301 / 491-492
属性位置解析 Rendering/OpenGL2/vtkShaderProgram.cxx 1075
使用程序/uniform Rendering/OpenGL2/vtkShaderProgram.cxx 510 / 557 / 663 / 757-1037
VBO 命名缓存 Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx 4004-4017
shader 源码生成 Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx 2098 / 2360 / 2377
矩阵 uniform Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx 2709 / 3038-3106
绘制调用 Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx 3478 / 3523-3525
相机矩阵转置 Rendering/OpenGL2/vtkOpenGLCamera.cxx 122-126

附录:关键源码位置索引

阶段 文件 行号 说明
上下文管理接口 Rendering/Core/vtkRenderWindow.h --- IsCurrent / SupportsOpenGL / IsDirect 等桩实现
OwnContext / 纯虚接口 Rendering/OpenGL2/vtkOpenGLRenderWindow.h --- OwnContext 标志、CreateAWindow / PushContext 声明
OwnContext 初始化 Rendering/OpenGL2/vtkOpenGLRenderWindow.cxx --- 默认 OwnContext = 1、Start() 中状态重置
事件化渲染窗口 Rendering/OpenGL2/vtkGenericOpenGLRenderWindow.h --- 事件化声明
事件化实现 Rendering/OpenGL2/vtkGenericOpenGLRenderWindow.cxx 79-100 / 187-215 MakeCurrent/IsCurrent、Render 的 Push/Pop
对象创建 GUISupport/Qt/QVTKOpenGLNativeWidget.cxx 27 / 35 / 78 / 90 构造、类型检查、setRenderWindow
上下文初始化 GUISupport/Qt/QVTKOpenGLNativeWidget.cxx 212-260 initializeGL:符号加载 + 适配器
符号加载 GUISupport/Qt/QVTKOpenGLNativeWidget.cxx 221-234 SetOpenGLSymbolLoader
上下文初始化 Rendering/OpenGL2/vtkOpenGLRenderWindow.cxx 811 / 826 / 904 OpenGLInit / InitState / InitContext
事件桥接 GUISupport/Qt/QVTKRenderWindowAdapter.cxx 78-148 / 451-486 观察者注册 / 事件分发
渲染入口 GUISupport/Qt/QVTKRenderWindowAdapter.cxx 241-266 paint(是否触发渲染)
主循环 Rendering/Core/vtkRenderWindow.cxx 414-472 Start/DoStereoRender/End/CopyResultFrame
帧尾 Rendering/Core/vtkRenderWindow.cxx 710-731 CopyResultFrame → Frame
FBO 解析 Rendering/OpenGL2/vtkOpenGLRenderWindow.cxx 1583-1633 Frame:resolve/flip/blit
剔除 Rendering/Core/vtkRenderer.cxx 244-428 Render:可见性 + AllocateTime + DeviceRender
设备渲染 Rendering/OpenGL2/vtkOpenGLRenderer.cxx 313-401 DeviceRender:Camera/Lights/Geometry
分阶段渲染 Rendering/OpenGL2/vtkOpenGLRenderer.cxx 405-545 UpdateGeometry:Opaque/Translucent/Volume/Overlay
清屏 Rendering/OpenGL2/vtkOpenGLRenderer.cxx 749 Clear(由 vtkOpenGLCamera::Render 调用)
Actor Rendering/Core/vtkActor.cxx 181 RenderOpaqueGeometry
Actor(GL) Rendering/OpenGL2/vtkOpenGLActor.cxx 44-101 DepthMask + mapper->Render
Mapper Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx 3621-3657 RenderPiece 总流程
Mapper Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx 3478-3554 RenderPieceDraw(glDrawRangeElements)
Mapper Rendering/OpenGL2/vtkOpenGLPolyDataMapper.cxx 3893 BuildBufferObjects(VBO/IBO)
上屏 GUISupport/Qt/QVTKRenderWindowAdapter.cxx 301-338 blit(内部 FBO → framebuffer 0)
退出清理 GUISupport/Qt/QVTKOpenGLNativeWidget.cxx 71-75 / 306-309 析构 + cleanupContext
退出清理 GUISupport/Qt/QVTKRenderWindowAdapter.cxx 150-166 移除观察者 + Finalize
退出清理 Rendering/OpenGL2/vtkOpenGLRenderWindow.cxx 722-790 ReleaseGraphicsResources
退出清理 Rendering/OpenGL2/vtkGenericOpenGLRenderWindow.cxx 28-39 / 65-71 析构 + Finalize
Win32 自管理实现 Rendering/OpenGL2/vtkWin32OpenGLRenderWindow.h --- WGL 三句柄声明
Win32 自管理实现 Rendering/OpenGL2/vtkWin32OpenGLRenderWindow.cxx --- MakeCurrent / SetupPixelFormatPaletteAndContext
WebAssembly 对照 Rendering/OpenGL2/vtkWebAssemblyOpenGLRenderWindow.cxx --- Emscripten WebGL2 实现
相关推荐
把我格子衫拿来13 天前
VTK 9.5 与 Qt 6 联合开发:从嵌入三维窗口到可维护的桌面架构
c++·python·qt·vtk·三维可视化
把我格子衫拿来13 天前
VTK 与 Qt 的 CMake 工程怎么组织?模块、工具链与二进制兼容排错
c++·qt·vtk·cmake·工程实践
把我格子衫拿来13 天前
鼠标点到哪里才算选中?Qt 与 VTK 的拾取坐标、DPI 与体素定位
python·qt·vtk·坐标变换·三维交互
把我格子衫拿来13 天前
VTK 体绘制入门到排错:传递函数、采样距离与透明度的关系
python·vtk·可视化·医学影像·体绘制
走好每一步5 个月前
12、VTK用X、Z平面去切割一个圆锥
平面·vtk·三维图像
走好每一步5 个月前
11、VTK重要数学知识:平面和法向量
平面·vtk·三维图像
走好每一步5 个月前
5、VTK刚体变换
qt·vtk·三维图像
走好每一步5 个月前
4、VDK读取xml模型数据
vtk·三维图像
走好每一步5 个月前
2、VDK 使用QVTKOpenGLNativeWidget嵌入到QT窗体中
qt·vtk·三维图像