VTK 渲染全流程与 OpenGL 上下文适配
基于 VTK v9.5.0 源码(
D:\vtk)整理,覆盖两个主题:
- 上下文适配原理 ------
QVTKOpenGLNativeWidget如何通过vtkGenericOpenGLRenderWindow借用 Qt 创建的 OpenGL 上下文;- 渲染全流程 ------ 从对象创建、上下文初始化、一帧渲染,到退出清理的完整调用链。
核心结论: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)做三件事:
SetReadyForRendering(false)------ 上下文就绪前禁止渲染;SetFrameBlitModeToNoBlit()------ 关键:VTK 只渲染到内部 FBO,不做硬件 blit,上屏交给paintGL;- 若无 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)完成真正桥接:
- 保存
QOpenGLContext与 surface; - 注册 6 个事件观察者(见第 4 节);
- 关掉 VTK 的
UseOffScreenBuffers(因为适配器自己用 FBO); SetForceMaximumHardwareLineWidth(1)、SetReadyForRendering(true)、SetOwnContext(0);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):
Draw关闭则直接返回;- BackingStore 命中且无修改 → 直接用缓存图
SetPixelData返回; - 可见性剔除 :遍历 props,把
GetVisibility()为真的放进PropArray; AllocateTime():调用 culler 排序、为每个 prop 分配渲染时间;ActiveCamera->UpdateIdealShiftScale();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)按顺序:
- 拾取分支 (
Selector存在时走硬件选择,不画真实场景); - 阴影分支 (
UseShadows→ shadow map pass); - 默认路径:
- Opaque 不透明几何 :
DeviceRenderOpaqueGeometry()(vtkOpenGLRenderer.cxx:570)→ 默认走Superclass::DeviceRenderOpaqueGeometry()→UpdateOpaquePolygonalGeometry()(遍历 props 调RenderOpaqueGeometry); - Translucent 半透明几何 :若有半透明 prop 再走
DeviceRenderTranslucentPolygonalGeometry(含深度剥离 depth peeling 选项);
- Opaque 不透明几何 :
- FXAA(若启用);
- 体绘制 :
RenderVolumetricGeometry; - 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):
- 注册窗口类 →
CreateWindowW创建HWND→GetDC拿HDC。 - 先建临时假窗口 + 假 context ,用
gladLoaderLoadWGL加载 WGL 扩展(wglChoosePixelFormatARB等必须先有 current context 才能解析)。 wglChoosePixelFormatARB按双缓冲 / 颜色 / 深度 / 模板 / 立体 / sRGB 选像素格式。- 优先
wglCreateContextAttribsARB从 4.5 往下逐个尝试建 3.2+ core context(对 Intel SandyBridge 有特判黑名单),失败回退wglCreateContext。 - 销毁临时假窗口 / 假 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 实现 |